/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see . *
* *
********************************************************************************/
#include "parse.h"
#include "express.h"
#include "character_decoder.h"
#include "exception.h"
#include "file.h"
#include "logger.h"
#include "schema.h"
#include "si_prefix.h"
#include "file_reader.h"
#include "utils.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Apple clang's libc++ has no floating-point std::from_chars overload (it's
// =deleted), so on macOS doubles are parsed via strtod_l with a cached "C"
// locale — locale-independent, unlike strtod. Other platforms (libstdc++,
// MSVC STL) have working float from_chars and are left unchanged.
#if defined(__APPLE__)
#include
#include
#endif
#ifdef USE_MMAP
#include
#endif
#define PERMISSIVE_FLOAT
using namespace ifcopenshell;
template
spf_lexer::spf_lexer(Reader* stream_, ::logger& log)
: decoder_(nullptr)
, logger_(log) {
stream = stream_;
decoder_ = new character_decoder(stream_, logger_);
}
template
spf_lexer::~spf_lexer() {
delete decoder_;
}
template
size_t spf_lexer::skip_whitespace() const {
size_t index = 0;
while (!stream->eof()) {
char character = stream->peek();
if ((character == ' ' || character == '\r' || character == '\n' || character == '\t')) {
stream->increment();
++index;
} else {
break;
}
}
return index;
}
template
size_t spf_lexer::skip_comment() const {
if (stream->eof()) {
return 0;
}
char character = stream->peek();
if (character != '/') {
return 0;
}
stream->increment();
character = stream->peek();
if (character != '*') {
stream->seek(stream->tell() - 1);
return 0;
}
size_t index = 2;
char intermediate = 0;
while (!stream->eof()) {
character = stream->peek();
stream->increment();
++index;
if (character == '/' && intermediate == '*') {
break;
}
intermediate = character;
}
return index;
}
template
std::string& spf_lexer::get_temp_string() const {
const size_t idx = pool_index++;
const size_t slice = idx >> 4;
const size_t offset = idx & 0xF;
while (stringpool_.size() <= slice) {
stringpool_.push_back(std::make_unique>());
}
// std::wcout << "Num contexts: " << idx << std::endl;
return (*stringpool_[slice])[offset];
}
namespace {
#if defined(__APPLE__) || defined(__EMSCRIPTEN__)
double parse_double_c(const char* start, char** end) {
static const locale_t loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
return strtod_l(start, end, loc);
}
#endif
template
bool parse_num_(const char* pStart, size_t size, T& val) {
if (size == 0) {
return false;
}
if (*pStart == '+') {
++pStart;
--size;
if (size == 0) {
return false;
}
}
if constexpr (std::is_floating_point_v) {
#if defined(__APPLE__) || defined(__EMSCRIPTEN__)
// pStart is NUL-terminated at pStart + size (callers pass c_str()), so
// strtod_l stops exactly at the end of a well-formed number. from_chars
// is not instantiated for double here — its float overload is =deleted
// in libc++ (Apple's and Emscripten's).
char* pEnd = nullptr;
const double result = parse_double_c(pStart, &pEnd);
if (pEnd != pStart + size) {
return false;
}
val = static_cast(result);
return true;
#else
auto re = std::from_chars(pStart, pStart + size, val);
return re.ec == std::errc() && re.ptr == pStart + size;
#endif
} else {
auto re = std::from_chars(pStart, pStart + size, val);
return re.ec == std::errc() && re.ptr == pStart + size;
}
}
} // namespace
namespace SWAR {
constexpr uint32_t ONES32 = 0x01010101u;
constexpr uint32_t HIGHS32 = 0x80808080u;
constexpr uint64_t ONES = 0x0101010101010101ull;
constexpr uint64_t HIGHS = 0x8080808080808080ull;
constexpr uint64_t splat(unsigned char c) {
return ONES * c;
}
inline uint32_t has_zero_byte(uint32_t x) {
return (x - ONES32) & ~x & HIGHS32;
}
inline uint64_t has_zero_byte(uint64_t x) {
return (x - ONES) & ~x & HIGHS;
}
inline uint32_t eq_mask(uint32_t x, uint32_t c) {
return has_zero_byte(x ^ c);
}
inline uint64_t eq_mask(uint64_t x, uint64_t c) {
return has_zero_byte(x ^ c);
}
namespace chars {
constexpr uint64_t lpar = splat('(');
constexpr uint64_t rpar = splat(')');
constexpr uint64_t eq = splat('=');
constexpr uint64_t comma = splat(',');
constexpr uint64_t semi = splat(';');
constexpr uint64_t slash = splat('/');
constexpr uint64_t space = splat(' ');
constexpr uint64_t cr = splat('\r');
constexpr uint64_t lf = splat('\n');
constexpr uint64_t tab = splat('\t');
constexpr uint64_t quote = splat('"');
constexpr uint64_t dot = splat('.');
} // namespace chars
template
inline uint64_t has_special_char(uint64_t x) {
return eq_mask(x, chars::lpar) |
eq_mask(x, chars::rpar) |
eq_mask(x, chars::eq) |
eq_mask(x, chars::comma) |
eq_mask(x, chars::semi) |
eq_mask(x, chars::slash) |
eq_mask(x, chars::space) |
eq_mask(x, chars::cr) |
eq_mask(x, chars::lf) |
eq_mask(x, chars::tab) |
eq_mask(x, chars::quote) |
(IncludeDot ? eq_mask(x, chars::dot) : uint64_t{0});
}
template
inline uint32_t has_special_char(uint32_t x) {
return eq_mask(x, static_cast(chars::lpar)) |
eq_mask(x, static_cast(chars::rpar)) |
eq_mask(x, static_cast(chars::eq)) |
eq_mask(x, static_cast(chars::comma)) |
eq_mask(x, static_cast(chars::semi)) |
eq_mask(x, static_cast(chars::slash)) |
eq_mask(x, static_cast(chars::space)) |
eq_mask(x, static_cast(chars::cr)) |
eq_mask(x, static_cast(chars::lf)) |
eq_mask(x, static_cast(chars::tab)) |
eq_mask(x, static_cast(chars::quote)) |
(IncludeDot ? eq_mask(x, static_cast(chars::dot)) : uint32_t{0});
}
}
//
// Returns the offset of the current token and moves cursor to next
//
template
token spf_lexer::next() {
if (stream->eof()) {
return token{};
}
auto pos = stream->tell();
char character = stream->read();
if (character == '/' || character == ' ' || character == '\r' || character == '\n' || character == '\t') {
while ((skip_whitespace() != 0U) || (skip_comment() != 0U)) {
}
if (stream->eof()) {
return token{};
}
pos = stream->tell();
character = stream->read();
}
// If the cursor is at [()=,;$*] we know token consists of single char
if (character == '(' ||
character == ')' ||
character == '=' ||
character == ',' ||
character == ';' ||
character == '$' ||
character == '*')
{
return token(pos, character);
}
auto& str = get_temp_string();
if (character == '\'') {
// If a string is encountered defer processing to the character_decoder
str = *decoder_;
return token(pos, token::Token_STRING, str);
} else {
auto ttype = token::Token_NONE;
if (character == '"' || character == '.') {
if (character == '"') {
ttype = token::Token_BINARY;
} else {
ttype = token::Token_ENUMERATION;
}
str.clear();
} else if (character == '#') {
ttype = token::Token_IDENTIFIER;
str.clear();
} else {
str.assign(&character, 1);
}
auto remaining = stream->remaining();
while (remaining) {
if (remaining >= 8) {
uint64_t x = stream->peek_u64();
if ((ttype == token::Token_NONE ? SWAR::has_special_char(x) : SWAR::has_special_char(x)) == 0) {
str.append(reinterpret_cast(&x), 8);
stream->increment(8);
remaining -= 8;
continue;
}
}
if (remaining >= 4) {
uint32_t x = stream->peek_u32();
if ((ttype == token::Token_NONE ? SWAR::has_special_char(x) : SWAR::has_special_char(x)) == 0) {
str.append(reinterpret_cast(&x), 4);
stream->increment(4);
remaining -= 4;
continue;
}
}
// Read character and increment pointer if not starting a new token
char character = stream->peek();
if (character == '(' ||
character == ')' ||
character == '=' ||
character == ',' ||
character == ';' ||
character == '/') {
break;
}
if (!(character == ' ' || character == '\r' || character == '\n' || character == '\t')) {
if ((ttype == token::Token_BINARY && character == '"') ||
(ttype == token::Token_ENUMERATION && character == '.')) {
// Skip
} else {
str.push_back(character);
}
}
stream->increment();
remaining -= 1;
}
if (ttype == token::Token_ENUMERATION && str.size() == 1 && (str[0] == 'T' || str[0] == 'F' || str[0] == 'U')) {
pop_pool_entry();
return token(pos, token::Token_BOOL, str[0]);
} else if (ttype == token::Token_IDENTIFIER) {
int int_val;
if (!parse_num_(str.c_str(), str.size(), int_val)) {
throw invalid_token_exception(pos, str, "instance name");
}
pop_pool_entry();
return token(pos, ttype, (int64_t)int_val);
} else if (ttype == token::Token_NONE && !str.empty()) {
int64_t int_val;
double float_val;
auto& first = str.front();
if ((first >= 'A' && first <= 'Z') || (first >= 'a' && first <= 'z')) {
ttype = token::Token_KEYWORD;
return token(pos, ttype, str);
} else if (parse_num_(str.c_str(), str.size(), int_val)) {
ttype = token::Token_INT;
pop_pool_entry();
return token(pos, ttype, int_val);
} else if (parse_num_(str.c_str(), str.size(), float_val)) {
ttype = token::Token_FLOAT;
pop_pool_entry();
return token(pos, float_val);
}
} else if (ttype == token::Token_BINARY || ttype == token::Token_ENUMERATION) {
return token(pos, ttype, str);
}
throw invalid_token_exception(pos, str, "valid token");
}
}
template class IFC_PARSE_API ifcopenshell::spf_lexer>;
template class IFC_PARSE_API ifcopenshell::spf_lexer>;
template class IFC_PARSE_API ifcopenshell::spf_lexer>;
#ifdef USE_MMAP
template class IFC_PARSE_API ifcopenshell::spf_lexer>;
#endif
bool token::is_operator() {
return type == Token_OPERATOR;
}
bool token::is_operator(char character) {
return type == Token_OPERATOR && value_char == character;
}
bool token::is_identifier() {
return type == Token_IDENTIFIER;
}
bool token::is_string() {
return type == Token_STRING;
}
bool token::is_enumeration() {
// @nb this is a bit confusing?
return type == Token_ENUMERATION || type == Token_BOOL;
}
bool token::is_binary() {
return type == Token_BINARY;
}
bool token::is_keyword() {
return type == Token_KEYWORD;
}
bool token::is_int() {
return type == Token_INT;
}
bool token::is_bool() {
// Bool and logical share the same storage type, just logical unknown is stored as 'U'.
return type == Token_BOOL && value_char != 'U';
}
bool token::is_logical() {
return type == Token_BOOL;
}
bool token::is_float() {
#ifdef PERMISSIVE_FLOAT
/// NB: We are being more permissive here then allowed by the standard
return type == Token_FLOAT || type == Token_INT;
#else
return type == Token_FLOAT;
#endif
}
int64_t token::as_int() {
if (type != Token_INT) {
throw invalid_token_exception(start_pos, to_string(), "integer");
}
return value_int;
}
unsigned token::as_identifier() {
if (type != Token_IDENTIFIER) {
throw invalid_token_exception(start_pos, to_string(), "instance name");
}
return (unsigned) value_int;
}
bool token::as_bool() {
if (type != Token_BOOL) {
throw invalid_token_exception(start_pos, to_string(), "boolean");
}
return value_char == 'T';
}
boost::logic::tribool token::as_logical() {
if (type != Token_BOOL) {
throw invalid_token_exception(start_pos, to_string(), "logical");
}
if (value_int == 'F') {
return false;
}
if (value_int == 'T') {
return true;
}
return boost::logic::indeterminate;
}
double token::as_float() {
#ifdef PERMISSIVE_FLOAT
if (type == Token_INT) {
/// NB: We are being more permissive here then allowed by the standard
return value_int;
} // ----> continues beyond preprocessor directive
#endif
if (type == Token_FLOAT) {
return value_double;
}
throw invalid_token_exception(start_pos, to_string(), "real");
}
const std::string& token::as_string() {
if (is_string() || is_enumeration() || is_binary() || is_keyword()) {
// @todo quotes
return *value_string;
}
throw invalid_token_exception(start_pos, to_string(), "string");
}
boost::dynamic_bitset<> token::as_binary() {
const std::string& str = as_string();
if (str.empty()) {
throw exception("token is not a valid binary sequence");
}
std::string::const_iterator it = str.begin();
int n = *it - '0';
if ((n < 0 || n > 3) || (str.size() == 1 && n != 0)) {
throw exception("token is not a valid binary sequence");
}
++it;
unsigned i = (str.size() - 1) * 4 - n;
boost::dynamic_bitset<> bitset(i);
for (; it != str.end(); ++it) {
const std::string::value_type& c = *it;
int value = (c < 'A') ? (c - '0') : (c - 'A' + 10);
for (unsigned j = 0; j < 4; ++j) {
if (i-- == 0) {
break;
}
if ((value & (1 << (3 - j))) != 0) {
bitset.set(i);
}
}
}
return bitset;
}
std::string token::to_string() {
std::string result;
if (type == Token_OPERATOR || type == Token_BOOL) {
result.push_back(value_char);
} else if (type == Token_INT) {
result = std::to_string(value_int);
} else if (type == Token_FLOAT) {
std::ostringstream oss;
oss << std::setprecision(15) << value_double;
result = oss.str();
} else {
return as_string();
}
return result;
}
namespace {
template
struct is_type_in_variant;
template
struct is_type_in_variant, T>
{
static constexpr bool value = std::is_same::value || is_type_in_variant, T>::value;
};
template
struct is_type_in_variant, T>
{
static constexpr bool value = std::is_same::value;
};
template
constexpr bool is_type_in_variant_v = is_type_in_variant::value;
class parameter_type_view {
const ifcopenshell::declaration* declaration_;
const std::vector* attributes_;
std::unique_ptr transient_named_type_;
public:
parameter_type_view(const ifcopenshell::declaration* declaration)
: declaration_(declaration)
, attributes_(nullptr)
{
if (declaration_ && declaration_->as_entity()) {
attributes_ = &declaration_->as_entity()->all_attributes();
} else if (declaration_ && declaration_->as_enumeration_type()) {
transient_named_type_.reset(new ifcopenshell::named_type(const_cast(declaration_)));
}
}
size_t size() const {
if (attributes_) {
return attributes_->size();
}
return declaration_ ? 1 : 0;
}
const ifcopenshell::parameter_type* operator[](size_t index) const {
if (attributes_) {
return index < attributes_->size() ? (*attributes_)[index]->type_of_attribute() : nullptr;
}
if (index != 0 || !declaration_) {
return nullptr;
}
if (auto* type_declaration = declaration_->as_type_declaration()) {
return type_declaration->declared_type();
}
if (declaration_->as_enumeration_type()) {
return transient_named_type_.get();
}
return nullptr;
}
};
const ifcopenshell::parameter_type* unwrap_type_declarations(const ifcopenshell::parameter_type* parameter_type) {
while (parameter_type && parameter_type->as_named_type() &&
parameter_type->as_named_type()->declared_type()->as_type_declaration()) {
parameter_type = parameter_type->as_named_type()->declared_type()->as_type_declaration()->declared_type();
}
return parameter_type;
}
ifcopenshell::declaration* declared_type(const ifcopenshell::parameter_type* parameter_type) {
parameter_type = unwrap_type_declarations(parameter_type);
return parameter_type && parameter_type->as_named_type() ? parameter_type->as_named_type()->declared_type() : nullptr;
}
const ifcopenshell::aggregation_type* aggregate_parameter_type(const ifcopenshell::parameter_type* parameter_type) {
parameter_type = unwrap_type_declarations(parameter_type);
return parameter_type ? parameter_type->as_aggregation_type() : nullptr;
}
const ifcopenshell::aggregation_type* nested_aggregation_type(const ifcopenshell::aggregation_type* aggregate_type) {
return aggregate_type ? aggregate_parameter_type(aggregate_type->type_of_element()) : nullptr;
}
void warn_attribute_count(
const ifcopenshell::declaration* declaration,
std::optional instance_name,
size_t expected_size,
size_t actual_size,
::logger& logger
) {
if (!declaration || expected_size == actual_size) {
return;
}
if (declaration->schema() == &Header_section_schema::get_schema()) {
logger.warning("VAL", 15, "Expected " + std::to_string(expected_size) + " attribute values, found " + std::to_string(actual_size) + " for header entity " + declaration->name());
} else {
logger.warning("VAL", 16, "Expected " + std::to_string(expected_size) + " attribute values, found " + std::to_string(actual_size) + (instance_name ? std::string(" for instance #" + std::to_string(*instance_name)) : std::string("")));
}
}
template
void dispatch_token_direct(ifcopenshell::token token, ifcopenshell::declaration* declaration, int attribute_index, logger& logger, Fn&& fn) {
if (token.is_binary()) {
fn(token.as_binary());
} else if (token.is_bool()) {
fn(token.as_bool());
} else if (token.is_logical()) {
fn(token.as_logical());
} else if (token.is_enumeration()) {
const auto& value = token.as_string();
if (declaration && declaration->as_enumeration_type()) {
try {
fn(enumeration_reference(declaration->as_enumeration_type(), declaration->as_enumeration_type()->lookup_enum_offset(value)));
} catch (ifcopenshell::exception&) {
logger.error("VAL", 12, "An enumeration literal '" + value + "' is not valid for type '" + declaration->name() + "' at offset " + std::to_string(token.start_pos));
}
} else {
logger.error("VAL", 13, "An enumeration literal '" + value + "' is not expected at attribute index '" + std::to_string(attribute_index) + "' at offset " + std::to_string(token.start_pos));
}
} else if (token.is_int()) {
fn(token.as_int());
} else if (token.is_float()) {
fn(token.as_float());
} else if (token.is_identifier()) {
fn(ifcopenshell::reference_or_simple_type{ifcopenshell::instance_reference{(int) token.as_identifier(), token.start_pos}});
} else if (token.is_string()) {
fn(token.as_string());
} else if (token.is_operator('*')) {
fn(derived{});
}
}
typedef std::variant<
blank,
std::vector,
std::vector,
std::vector,
std::vector>,
std::vector,
std::vector>,
std::vector>,
std::vector>
> direct_aggregate_storage;
struct direct_aggregate {
direct_aggregate_storage storage;
size_t pending_empty_aggregates = 0;
size_t values = 0;
::logger& logger_;
explicit direct_aggregate(::logger& logger)
: logger_(logger) {}
template
void append(const T& value) {
++values;
if constexpr (is_type_in_variant_v>>) {
if constexpr (
std::is_same_v, std::vector> ||
std::is_same_v, std::vector> ||
std::is_same_v, std::vector>
) {
if (storage.index() == 0 && pending_empty_aggregates) {
append_promoted(value);
return;
}
}
if (pending_empty_aggregates) {
logger_.error("VAL", 14, "Inconsistent aggregate valuation while attempting to append " + std::string(typeid(T).name()) + " after an empty nested aggregate");
pending_empty_aggregates = 0;
}
if (storage.index() == 0) {
storage = std::vector>{value};
} else if (auto* vector = std::get_if>>(&storage)) {
vector->push_back(value);
} else {
append_promoted(value);
}
} else {
logger_.error("UNS", 31, std::string("Aggregates of ") + typeid(T).name() + " are not supported in the IfcOpenShell parser");
}
}
void append_empty_nested() {
++values;
if (auto* int_vector = std::get_if>>(&storage)) {
int_vector->emplace_back();
} else if (auto* double_vector = std::get_if>>(&storage)) {
double_vector->emplace_back();
} else if (auto* reference_vector = std::get_if>>(&storage)) {
reference_vector->emplace_back();
} else if (storage.index() == 0) {
++pending_empty_aggregates;
} else {
logger_.error("Inconsistent aggregate valuation while attempting to append an empty nested aggregate");
}
}
private:
template
void append_promoted(const T& value) {
if constexpr (std::is_same_v, int64_t>) {
if (auto* vector = std::get_if>(&storage)) {
vector->push_back((double) value);
return;
}
}
if constexpr (std::is_same_v, double>) {
if (auto* vector = std::get_if>(&storage)) {
std::vector promoted(vector->begin(), vector->end());
promoted.push_back(value);
storage = std::move(promoted);
return;
}
}
if constexpr (std::is_same_v, std::vector>) {
if (storage.index() == 0) {
std::vector> promoted(pending_empty_aggregates);
pending_empty_aggregates = 0;
promoted.push_back(value);
storage = std::move(promoted);
return;
}
if (auto* vector = std::get_if>>(&storage)) {
vector->push_back(value);
return;
}
if (auto* vector = std::get_if>>(&storage)) {
std::vector promoted(value.begin(), value.end());
vector->push_back(std::move(promoted));
return;
}
}
if constexpr (std::is_same_v, std::vector>) {
if (storage.index() == 0) {
std::vector> promoted(pending_empty_aggregates);
pending_empty_aggregates = 0;
promoted.push_back(value);
storage = std::move(promoted);
return;
}
if (auto* vector = std::get_if>>(&storage)) {
vector->push_back(value);
return;
}
if (auto* vector = std::get_if>>(&storage)) {
std::vector> promoted;
promoted.reserve(vector->size() + 1);
for (const auto& nested : *vector) {
promoted.emplace_back(nested.begin(), nested.end());
}
promoted.push_back(value);
storage = std::move(promoted);
return;
}
}
if constexpr (std::is_same_v, std::vector>) {
if (storage.index() == 0) {
std::vector> promoted(pending_empty_aggregates);
pending_empty_aggregates = 0;
promoted.push_back(value);
storage = std::move(promoted);
return;
}
if (auto* vector = std::get_if>>(&storage)) {
vector->push_back(value);
return;
}
}
auto current = std::visit([](auto v) {
if constexpr (!std::is_same_v) {
return std::string(typeid(typename decltype(v)::value_type).name());
} else {
return std::string{};
}
}, storage);
logger_.error("Inconsistent aggregate valuation while attempting to append " + std::string(typeid(T).name()) + " to an aggregate of " + current);
}
};
void append_empty_direct_aggregate(const ifcopenshell::aggregation_type* aggregate_type, direct_aggregate& target) {
if (!aggregate_type) {
target.append_empty_nested();
return;
}
auto argument_type = ifcopenshell::make_aggregate(ifcopenshell::from_parameter_type(aggregate_type->type_of_element()));
if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_INT) {
target.storage = std::vector{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_DOUBLE) {
target.storage = std::vector{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_STRING) {
target.storage = std::vector{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_BINARY) {
target.storage = std::vector>{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE) {
target.storage = std::vector{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_INT) {
target.storage = std::vector>{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_DOUBLE) {
target.storage = std::vector>{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE) {
target.storage = std::vector>{};
} else {
target.append_empty_nested();
}
}
template
void set_direct_attribute(
in_memory_attribute_storage& storage,
std::optional instance_name,
ifcopenshell::unresolved_references* references_to_resolve,
size_t attribute_index,
int resolve_reference_index,
const T& value
) {
if constexpr (std::is_same_v, ifcopenshell::reference_or_simple_type>) {
if (instance_name && references_to_resolve) {
references_to_resolve->push_back(std::make_pair(
mutable_attribute_value{(uint32_t) *instance_name, resolve_reference_index == -1 ? (uint8_t) attribute_index : (uint8_t) resolve_reference_index},
value
));
}
} else if constexpr (std::is_same_v, std::vector>) {
if (instance_name && references_to_resolve) {
references_to_resolve->push_back({{(uint32_t) *instance_name, resolve_reference_index == -1 ? (uint8_t) attribute_index : (uint8_t) resolve_reference_index}, value});
}
} else if constexpr (std::is_same_v, std::vector>>) {
if (instance_name && references_to_resolve) {
references_to_resolve->push_back({{(uint32_t) *instance_name, resolve_reference_index == -1 ? (uint8_t) attribute_index : (uint8_t) resolve_reference_index}, value});
}
} else {
storage.set(attribute_index, value);
}
}
template
void skip_aggregate(ifcopenshell::spf_lexer* tokens) {
size_t depth = 1;
while (depth) {
token next = tokens->next();
if (!next) {
break;
}
if (next.is_operator('(')) {
++depth;
} else if (next.is_operator(')')) {
--depth;
}
}
}
template
direct_aggregate read_direct_aggregate(
ifcopenshell::impl::in_memory_file_storage& storage,
ifcopenshell::spf_lexer* tokens,
std::optional entity_instance_name,
const ifcopenshell::entity* entity,
int attribute_index,
const ifcopenshell::aggregation_type* aggregate_type,
::logger& logger
) {
direct_aggregate aggregate(logger);
token next = tokens->next();
while (next) {
if (next.is_operator(',')) {
} else if (next.is_operator(')')) {
break;
} else if (next.is_operator('(')) {
auto nested = read_direct_aggregate(storage, tokens, entity_instance_name, entity, attribute_index, nested_aggregation_type(aggregate_type), logger);
if (nested.values == 0 && nested.storage.index() == 0) {
aggregate.append_empty_nested();
} else {
std::visit([&aggregate](const auto& value) {
if constexpr (!std::is_same_v, blank>) {
aggregate.append(value);
}
}, nested.storage);
}
} else if (next.is_keyword()) {
try {
const auto* declaration = (storage.schema ? storage.schema : storage.file->schema())->declaration_by_name(next.as_string());
tokens->next();
auto data = storage.load(tokens, entity_instance_name, declaration, entity, attribute_index);
storage.read_simple_type_instances.push_back(data);
aggregate.append(ifcopenshell::reference_or_simple_type{express::Base(data)});
} catch (exception& e) {
logger.error("SYN", 123, std::string(e.what()) + " at offset " + std::to_string(next.start_pos));
}
} else {
if (next.is_identifier() && entity && entity_instance_name) {
storage.register_inverse((unsigned)*entity_instance_name, entity, next.value_int, attribute_index);
}
dispatch_token_direct(next, aggregate_type && aggregate_type->type_of_element()->as_named_type() ? aggregate_type->type_of_element()->as_named_type()->declared_type() : nullptr, attribute_index, logger, [&aggregate](const auto& value) {
aggregate.append(value);
});
}
next = tokens->next();
}
if (aggregate.values == 0) {
append_empty_direct_aggregate(aggregate_type, aggregate);
}
return aggregate;
}
} // namespace
//
// Reads the arguments from a list of tokens directly into instance_data storage.
// Additionally, registers the ids (i.e. #[\d]+) in the inverse map.
//
template
shared_pointer_type ifcopenshell::impl::in_memory_file_storage::load(
ifcopenshell::spf_lexer* tokens,
std::optional entity_instance_name,
const ifcopenshell::declaration* declaration,
const ifcopenshell::entity* entity,
int attribute_index,
bool coerce_attribute_count
) {
static_cast(coerce_attribute_count);
parameter_type_view parameter_types(declaration);
const size_t expected_size = parameter_types.size();
in_memory_attribute_storage storage(expected_size);
token next = tokens->next();
size_t attribute_index_within_data = 0;
size_t values_read = 0;
while (next) {
if (next.is_operator(',')) {
++attribute_index_within_data;
} else if (next.is_operator(')')) {
break;
} else {
++values_read;
const bool retain_value = attribute_index_within_data < expected_size;
const ifcopenshell::parameter_type* parameter_type = retain_value ? parameter_types[attribute_index_within_data] : nullptr;
const int reference_attribute_index = attribute_index == -1 ? (int) attribute_index_within_data : attribute_index;
if (next.is_operator('(')) {
if (retain_value) {
auto aggregate = read_direct_aggregate(*this, tokens, entity_instance_name, entity, reference_attribute_index, aggregate_parameter_type(parameter_type), logger_.get());
std::visit([&](const auto& value) {
if constexpr (!std::is_same_v, blank>) {
set_direct_attribute(storage, entity_instance_name, references_to_resolve, attribute_index_within_data, attribute_index, value);
}
}, aggregate.storage);
} else {
skip_aggregate(tokens);
}
} else if (next.is_keyword()) {
try {
const auto* simple_declaration = (schema ? schema : file->schema())->declaration_by_name(next.as_string());
tokens->next();
if (retain_value) {
auto data = load(tokens, entity_instance_name, simple_declaration, entity, reference_attribute_index);
read_simple_type_instances.push_back(data);
storage.set(attribute_index_within_data, express::Base(data));
} else {
skip_aggregate(tokens);
}
} catch (exception& e) {
logger_.get().message(::logger::LOG_ERROR, std::string(e.what()) + " at offset " + std::to_string(next.start_pos));
--values_read;
}
} else {
if (next.is_identifier() && entity && entity_instance_name) {
register_inverse((unsigned)*entity_instance_name, entity, next.value_int, reference_attribute_index);
}
if (retain_value) {
dispatch_token_direct(next, declared_type(parameter_type), (int) attribute_index_within_data, logger_.get(), [&](const auto& value) {
set_direct_attribute(storage, entity_instance_name, references_to_resolve, attribute_index_within_data, attribute_index, value);
});
}
}
}
next = tokens->next();
}
warn_attribute_count(declaration, entity_instance_name, expected_size, values_read, logger_.get());
return ifcopenshell::make_pointer_type(file, declaration, (declaration && declaration->as_entity()) ? (uint32_t)entity_instance_name.value_or(0) : 0, std::move(storage));
}
template
void ifcopenshell::impl::in_memory_file_storage::try_read_semicolon(ifcopenshell::spf_lexer* tokens) const {
auto old_offset = tokens->stream->tell();
token semilocon = tokens->next();
if (!semilocon.is_operator(';')) {
tokens->stream->seek(old_offset);
}
}
void ifcopenshell::impl::in_memory_file_storage::register_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, int inst_id, int attribute_index) {
// Assume a check on token type has already been performed
byref_excl_.add((uint32_t)inst_id, (uint32_t)id_from, (uint16_t)from_entity->index_in_schema(), attribute_index);
}
void ifcopenshell::impl::in_memory_file_storage::unregister_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, const express::Base& inst, int attribute_index) {
if (!byref_excl_.remove((uint32_t)inst.id(), (uint32_t)id_from, (uint16_t)from_entity->index_in_schema(), attribute_index)) {
// @todo inverses also need to be populated when multiple instances are added to a new file.
// throw ifcopenshell::exception("Instance not found among inverses");
}
}
namespace {
template
std::string to_string_fixed_width(const T& t, size_t) {
// @todo currently inactive
std::ostringstream oss;
oss << /*std::setfill('0') << std::setw(w) <<*/ t;
return oss.str();
}
}
void ifcopenshell::impl::rocks_db_file_storage::register_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, int inst_id, int attribute_index) {
#ifdef IFOPSH_WITH_ROCKSDB
static std::string s;
uint32_t v = id_from;
s.resize(sizeof(uint32_t));
memcpy(s.data(), &v, sizeof(uint32_t));
auto key = "v|" + to_string_fixed_width(inst_id, 10) + "|" + to_string_fixed_width(from_entity->index_in_schema(), 4) + "|" + to_string_fixed_width(attribute_index, 2);
db->Merge(wopts, key, s);
/*
// Python client does not support merges
// @todo turn this into a setting
{
std::string current;
db->Get(rocksdb::ReadOptions{}, key, ¤t);
auto new_val = current + s;
db->Put(wopts, key, new_val);
}*/
#endif
}
void ifcopenshell::impl::rocks_db_file_storage::unregister_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, const express::Base& inst, int attribute_index) {
#ifdef IFOPSH_WITH_ROCKSDB
static std::string s;
auto inst_id = inst.id();
auto key = "v|" + to_string_fixed_width(inst_id, 10) + "|" + to_string_fixed_width(from_entity->index_in_schema(), 4) + "|" + to_string_fixed_width(attribute_index, 2);
if (db->Get(rocksdb::ReadOptions{}, key, &s).ok()) {
std::vector vals(s.size() / sizeof(uint32_t));
memcpy(vals.data(), s.data(), s.size());
auto it = std::find(vals.begin(), vals.end(), (uint32_t)id_from);
if (it != vals.end()) {
vals.erase(it);
} else {
file->logger().error("Unregistering non-existant inverse #" + std::to_string(id_from) + " on instance #" + std::to_string(inst_id) + " at attribute " + std::to_string(attribute_index));
}
s.resize(vals.size() * sizeof(uint32_t));
memcpy(s.data(), vals.data(), s.size());
db->Put(wopts, key, s);
}
#endif
}
void ifcopenshell::impl::rocks_db_file_storage::add_type_ref(const express::Base& new_entity)
{
#ifdef IFOPSH_WITH_ROCKSDB
size_t v;
std::string s(sizeof(size_t), ' ');
if (new_entity.declaration().as_entity()) {
v = new_entity.id();
memcpy(s.data(), &v, sizeof(size_t));
// no merges yet, because the python client doesn't support them
db->Merge(wopts, "t|" + std::to_string(new_entity.declaration().index_in_schema()), s);
/*{
std::string current;
// @todo this uses the same key-namespace as typedecl instances, not a direct conflict, but also not very clear
auto key = "t|" + std::to_string(new_entity.declaration().index_in_schema());
db->Get(rocksdb::ReadOptions{}, key, ¤t);
auto new_val = current + s;
db->Put(wopts, key, new_val);
}*/
}
// not only mapping also register type
v = new_entity.declaration().index_in_schema();
memcpy(s.data(), &v, sizeof(size_t));
db->Put(wopts, (new_entity.declaration().as_entity() ? "i|" : "t|") + std::to_string(new_entity.id() ? new_entity.id() : new_entity.identity()) + "|_", s);
#endif
}
void ifcopenshell::impl::rocks_db_file_storage::remove_type_ref(const express::Base& new_entity)
{
#ifdef IFOPSH_WITH_ROCKSDB
if (new_entity.declaration().as_entity()) {
std::string s;
auto key = "t|" + std::to_string(new_entity.declaration().index_in_schema());
if (db->Get(rocksdb::ReadOptions{}, key, &s).ok()) {
std::vector vals(s.size() / sizeof(size_t));
memcpy(vals.data(), s.data(), s.size());
vals.erase(std::find(vals.begin(), vals.end(), (size_t)new_entity.id()));
s.resize(vals.size() * sizeof(size_t));
memcpy(s.data(), vals.data(), s.size());
db->Put(wopts, key, s);
}
}
db->Delete(wopts, (new_entity.declaration().as_entity() ? "i|" : "t|") + std::to_string(new_entity.id() ? new_entity.id() : new_entity.identity()) + "|_");
#endif
}
namespace {
class StringBuilderVisitor : public boost::static_visitor {
private:
StringBuilderVisitor(const StringBuilderVisitor&); //N/A
StringBuilderVisitor& operator=(const StringBuilderVisitor&); //N/A
std::ostream& data_;
template
void serialize(const std::vector& i) {
data_ << "(";
for (typename std::vector::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
data_ << *it;
}
data_ << ")";
}
// Shortest decimal representation of 'd' that round-trips back to the
// exact same double (like std::to_chars, or Python's repr).
// Using actual `std::to_chars` requires macOS 13.3+, so we implement this manually,
// until we drop support for older targets.
//
// Mirrors libstdc++'s notation-choice bounds (floating_to_chars.cc,
// __floating_to_chars_shortest) to pick whichever of fixed/scientific is
// shorter for a given digit count and exponent.
static inline void format_double_shortest(char (&buf)[64], double d) {
char sci[64];
int mantissa_length = 17;
for (int prec = 1; prec <= 17; ++prec) {
snprintf(sci, sizeof(sci), "%.*e", prec - 1, d);
if (strtod(sci, nullptr) == d) {
mantissa_length = prec;
break;
}
}
const char* exp_str = strchr(sci, 'e');
const int scientific_exponent = exp_str ? atoi(exp_str + 1) : 0;
const int fd_exponent = scientific_exponent - (mantissa_length - 1);
int lower_bound = -(mantissa_length + 3);
int upper_bound = 5;
if (mantissa_length == 1) {
++lower_bound;
--upper_bound;
}
if (fd_exponent >= lower_bound && fd_exponent <= upper_bound) {
const int fixed_precision = fd_exponent < 0 ? -fd_exponent : 0;
snprintf(buf, 64, "%.*f", fixed_precision, d);
} else {
snprintf(buf, 64, "%.*e", mantissa_length - 1, d);
}
}
// The REAL token definition from the IFC SPF standard does not necessarily match
// the output of the C++ ostream formatting operation.
// REAL = [ SIGN ] DIGIT { DIGIT } "." { DIGIT } [ "E" [ SIGN ] DIGIT { DIGIT } ] .
static std::string format_double(const double& d) {
// Use the shortest representation that round-trips exactly (like
// Python's repr) instead of max_digits10. max_digits10 padded clean
// values with noise digits (0.0174532925199433 -> 0.017453292519943299),
// which rewrote every REAL and produced huge diffs when a file was
// re-saved. See #7696.
char buf[64];
format_double_shortest(buf, d);
const std::string str(buf);
std::string::size_type e = str.find('e');
if (e == std::string::npos) {
e = str.find('E');
}
std::string result = str.substr(0, e);
if (result.find('.') == std::string::npos) {
result += '.';
}
if (e != std::string::npos) {
result += 'E';
result += str.substr(e + 1);
}
return result;
}
static std::string format_binary(const boost::dynamic_bitset<>& b) {
std::ostringstream oss;
oss.imbue(std::locale::classic());
oss.put('"');
oss << std::uppercase << std::hex << std::setw(1);
unsigned c = (unsigned)b.size();
unsigned n = (4 - (c % 4)) & 3;
oss << n;
for (unsigned i = 0; i < c + n;) {
unsigned accum = 0;
for (int j = 0; j < 4; ++j, ++i) {
unsigned bit = i < n ? 0 : b.test(c - i + n - 1) ? 1
: 0;
accum |= bit << (3 - j);
}
oss << accum;
}
oss.put('"');
return oss.str();
}
bool upper_;
public:
StringBuilderVisitor(std::ostream& stream, bool upper = false)
: data_(stream),
upper_(upper) {}
void operator()(const blank& /*i*/) { data_ << "$"; }
void operator()(const derived& /*i*/) { data_ << "*"; }
void operator()(const int64_t& i) { data_ << i; }
void operator()(const bool& i) { data_ << (i ? ".T." : ".F."); }
void operator()(const boost::logic::tribool& i) { data_ << (i ? ".T." : (boost::logic::indeterminate(i) ? ".U." : ".F.")); }
void operator()(const double& i) { data_ << format_double(i); }
void operator()(const boost::dynamic_bitset<>& i) { data_ << format_binary(i); }
void operator()(const std::string& i) {
std::string s = i;
if (upper_) {
data_ << static_cast(character_encoder(s));
} else {
data_ << '\'' << s << '\'';
}
}
void operator()(const std::vector& i);
void operator()(const std::vector& i);
void operator()(const std::vector& i);
void operator()(const std::vector>& i);
void operator()(const enumeration_reference& i) {
data_ << "." << i.value() << ".";
}
void operator()(const express::Base& i) {
if (i.declaration().as_entity() == nullptr || i.declaration().schema() == &Header_section_schema::get_schema()) {
i.to_string(data_, upper_);
} else {
data_ << "#" << i.id();
}
}
void operator()(const std::vector& i) {
data_ << "(";
for (auto it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
(*this)(*it);
}
data_ << ")";
}
void operator()(const std::vector>& i);
void operator()(const std::vector>& i);
void operator()(const std::vector>& i) {
data_ << "(";
for (auto outer_it = i.begin(); outer_it != i.end(); ++outer_it) {
if (outer_it != i.begin()) {
data_ << ",";
}
data_ << "(";
for (auto inner_it = outer_it->begin(); inner_it != outer_it->end(); ++inner_it) {
if (inner_it != outer_it->begin()) {
data_ << ",";
}
(*this)(*inner_it);
}
data_ << ")";
}
data_ << ")";
}
void operator()(const empty_aggregate_t& /*unused*/) const { data_ << "()"; }
void operator()(const empty_aggregate_of_aggregate_t& /*unused*/) const { data_ << "()"; }
};
template <>
void StringBuilderVisitor::serialize(const std::vector& i) {
data_ << "(";
for (std::vector::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
std::string encoder = character_encoder(*it);
data_ << encoder;
}
data_ << ")";
}
template <>
void StringBuilderVisitor::serialize(const std::vector& i) {
data_ << "(";
for (std::vector::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
data_ << format_double(*it);
}
data_ << ")";
}
template <>
void StringBuilderVisitor::serialize(const std::vector>& i) {
data_ << "(";
for (std::vector>::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
data_ << format_binary(*it);
}
data_ << ")";
}
void StringBuilderVisitor::operator()(const std::vector& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector>& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector>& i) {
data_ << "(";
for (std::vector>::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
serialize(*it);
}
data_ << ")";
}
void StringBuilderVisitor::operator()(const std::vector>& i) {
data_ << "(";
for (std::vector>::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
serialize(*it);
}
data_ << ")";
}
}
//
// Returns a string representation of the entity
// Note that this initializes the entity if it is not initialized
//
void instance_data::to_string(std::ostream& ss, bool upper) const {
ss.imbue(std::locale::classic());
ss << "(";
StringBuilderVisitor vis(ss, upper);
// In almost all cases, storage is initialized with the size of the schema declaration,
// apparently except in case of header entities and invalid in-line type declarations.
auto size = (declaration_ && declaration_->as_entity() ? declaration_->as_entity()->attribute_count() : 1);
if (storage_) {
size = (std::min)(size, storage_->size());
}
for (size_t i = 0; i < size; ++i) {
if (i != 0) {
ss << ",";
}
if (has_attribute_value(i)) {
if (declaration_ != nullptr && declaration_->as_entity() && declaration_->as_entity()->derived()[i]) {
ss << "*";
} else {
ss << "$";
}
} else {
get_attribute_value(i).apply_visitor(vis);
}
}
ss << ")";
}
/*
unsigned ifcopenshell::IfcBaseEntity::set_id(const std::optional& i) {
if (i) {
return id_ = *i;
}
return id_ = file_->fresh_id();
}
*/
namespace {
// @todo remove redundancy with python wrapper code (which is not identical due to
// different handling of enumerations)
ifcopenshell::argument_type get_argument_type(const ifcopenshell::declaration* decl, size_t i) {
const ifcopenshell::parameter_type* pt = 0;
if (decl->as_entity() != nullptr) {
pt = decl->as_entity()->attribute_by_index(i)->type_of_attribute();
if (decl->as_entity()->derived()[i]) {
return ifcopenshell::Argument_DERIVED;
}
} else if ((decl->as_type_declaration() != nullptr) && i == 0) {
pt = decl->as_type_declaration()->declared_type();
} else if ((decl->as_enumeration_type() != nullptr) && i == 0) {
return ifcopenshell::Argument_ENUMERATION;
}
if (pt == 0) {
return ifcopenshell::Argument_UNKNOWN;
}
return ifcopenshell::from_parameter_type(pt);
}
} // namespace
class unregister_inverse_visitor {
private:
file& file_;
const express::Base data_;
public:
unregister_inverse_visitor(file& file, const express::Base& data)
: file_(file),
data_(data) {}
void operator()(const express::Base& inst, int index) {
file_.unregister_inverse(data_.id(), data_.declaration().as_entity(), inst, index);
}
};
class register_inverse_visitor {
private:
file& file_;
const express::Base data_;
public:
register_inverse_visitor(file& file, const express::Base& data)
: file_(file),
data_(data) {}
void operator()(const express::Base& inst, int index) {
file_.register_inverse(data_.id(), data_.declaration().as_entity(), inst.id(), index);
}
};
class add_to_instance_list_visitor {
private:
std::vector* list_;
public:
add_to_instance_list_visitor(std::vector* list)
: list_(list) {}
void operator()(const express::Base& inst) {
list_->push_back(inst);
}
};
class apply_individual_instance_visitor {
private:
std::optional attribute_;
int attribute_index_;
const express::Base inst_;
template
void apply_attribute_(T& t, const attribute_value& attr, int index) const {
switch (attr.type()) {
case ifcopenshell::Argument_ENTITY_INSTANCE: {
express::Base inst = attr;
t(inst, index);
break;
}
case ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE: {
std::vector entity_list_attribute = attr;
for (auto& inst : entity_list_attribute) {
t(inst, index);
}
break;
}
case ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE: {
std::vector> nested_list_attr = attr;
for (auto& vec : nested_list_attr) {
for (auto& inst : vec) {
t(inst, index);
}
}
break;
}
default:
break;
}
}
public:
apply_individual_instance_visitor(const attribute_value& attribute, int idx)
: attribute_(attribute)
, attribute_index_(idx)
{}
apply_individual_instance_visitor(const express::Base& data)
: inst_(data)
{}
template
void apply(T& t) const {
if (attribute_) {
apply_attribute_(t, *attribute_, attribute_index_);
} else {
const auto& decl = inst_.declaration();
for (size_t i = 0; i < (decl.as_entity() ? decl.as_entity()->attribute_count() : 1); ++i) {
auto attr = inst_.get_attribute_value(i);
apply_attribute_(t, attr, (int) i);
}
}
};
};
template
typename std::enable_if<
(!std::is_base_of_v || std::is_same_v),
void>::type
express::Base::set_attribute_value(size_t i, const T& t) {
if constexpr (std::is_same_v, double>) {
if (!std::isfinite(t)) {
throw ifcopenshell::exception("Only finite values are allowed");
}
}
if constexpr (std::is_same_v, std::vector>) {
if (std::any_of(t.begin(), t.end(), [](double d) { return !std::isfinite(d); })) {
throw ifcopenshell::exception("Only finite values are allowed");
}
}
if constexpr (std::is_same_v, std::vector>>) {
for (auto& tt : t) {
if (std::any_of(tt.begin(), tt.end(), [](double d) { return !std::isfinite(d); })) {
throw ifcopenshell::exception("Only finite values are allowed");
}
}
}
auto current_attribute = get_attribute_value(i);
// Deregister old attribute guid in file guid map.
if (i == 0 && (file()->ifcroot_type() != nullptr) && this->declaration().is(*file()->ifcroot_type())) {
try {
auto guid = (std::string) current_attribute;
auto it = file()->internal_guid_map().find(guid);
if (it != file()->internal_guid_map().end()) {
const std::pair& p = *it;
if (p.second == *this) {
file()->internal_guid_map().erase(it);
}
}
} catch (ifcopenshell::exception& e) {
file()->logger().error(e);
}
}
if constexpr (std::is_same_v || std::is_same_v> || std::is_same_v>> || std::is_same_v) {
// Deregister inverse indices in file
unregister_inverse_visitor visitor(*file(), *this);
apply_individual_instance_visitor(current_attribute, (int)i).apply(visitor);
}
{
void* const storage = std::visit([](const auto& m) { return (void*)&m; }, file()->storage_);
data()->set_attribute_value(i, t);
}
auto new_attribute = get_attribute_value(i);
// Register inverse indices in file
if constexpr (std::is_same_v || std::is_same_v> || std::is_same_v>>) {
register_inverse_visitor visitor(*file(), *this);
apply_individual_instance_visitor(new_attribute, (int)i).apply(visitor);
}
// Register new attribute guid in guid map
if (i == 0 && (file()->ifcroot_type() != nullptr) && this->declaration().is(*file()->ifcroot_type())) {
try {
auto guid = (std::string) new_attribute;
auto it = file()->internal_guid_map().find(guid);
if (it != file()->internal_guid_map().end()) {
file()->logger().warning("Duplicate guid " + guid);
}
file()->internal_guid_map().insert({guid, *this});
} catch (ifcopenshell::exception& e) {
file()->logger().error(e);
}
}
}
template
typename std::enable_if<
(!std::is_base_of_v || std::is_same_v),
void>::type
express::Base::set_attribute_value(const std::string& s, const T& t)
{
set_attribute_value(declaration().as_entity()->attribute_index(s), t);
}
//
// Parses the IFC file in fn
// Creates the maps
//
#ifdef USE_MMAP
file::file(const std::string& fn, bool mmap, ::logger& log)
: logger_(log)
, schema_(nullptr)
, ifcroot_type_(nullptr)
, max_id_(0) {
initialize(fn, mmap);
}
bool ifcopenshell::file::initialize(const std::string& fn, bool mmap) {
if (mmap) {
file_reader s(fn);
storage_.emplace<1>(this, logger_.get());
std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
} else {
file_reader s(fn);
storage_.emplace<1>(this, logger_.get());
std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
}
if ((good_ = std::get(storage_).good_)) {
// @todo unify these names, it's already confusing enough as it stands
byid_ = decltype(byid_)(&std::get(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
}
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
header_.reset(new spf_header(this, &logger_.get()));
return good_ == file_open_status::SUCCESS;
}
#endif
file::file(const uninitialized_tag&, ::logger& log)
: good_(file_open_status::UNKNOWN), logger_(log), schema_(nullptr), ifcroot_type_(nullptr), max_id_(0), header_(nullptr) {}
bool ifcopenshell::file::initialize(const std::string& path, filetype ty, bool readonly) {
if (ty == FT_AUTODETECT) {
ty = guess_file_type(path);
}
if (ty == FT_IFCSPF) {
file_reader s(path);
storage_.emplace<1>(this, logger_.get());
std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
if ((good_ = std::get(storage_).good_)) {
// @todo unify these names, it's already confusing enough as it stands
byid_ = decltype(byid_)(&std::get(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
}
// byidentity_ = decltype(byidentity_)(&std::get(storage_).byidentity_);
} else if (ty == FT_ROCKSDB) {
// This would make some difference, but in the greater light of things, not really significant
// LateBoundEntity is also still large per instance
// instantiate_typed_instances = false;
// @todo this can only be used for databases that already exist, because otherwise there is no way to specify the schema
storage_.emplace<2>(path, this, readonly);
if (std::get(storage_).db == nullptr) {
storage_.emplace<0>();
good_ = file_open_status::READ_ERROR;
} else {
if (std::get(storage_).read_schema(schema_)) {
byid_ = decltype(byid_)(&std::get(storage_).instance_by_name_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
good_ = file_open_status::SUCCESS;
} else {
good_ = file_open_status::UNSUPPORTED_SCHEMA;
}
}
// byidentity_ = decltype(byidentity_)(&std::get(storage_).instance_cache_);
} else {
storage_.emplace<0>();
good_ = file_open_status::READ_ERROR;
// throw std::runtime_error("Unsupported file format");
}
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
header_.reset(new spf_header(this, &logger_.get()));
return good_ == file_open_status::SUCCESS;
}
void ifcopenshell::file::bypass_type(const std::string& type_name) {
types_to_bypass_loading_.insert(type_name);
}
file::file(const std::string& path, filetype ty, bool readonly, ::logger& log)
: logger_(log)
, schema_(nullptr)
, ifcroot_type_(nullptr)
, max_id_(0)
{
initialize(path, ty, readonly);
}
file::file(std::istream& stream, int length, ::logger& log)
: logger_(log)
, schema_(nullptr)
, ifcroot_type_(nullptr)
, max_id_(0)
{
file_reader s(caller_fed_tag{});
std::string string_data;
string_data.resize(length);
stream.read(string_data.data(), length);
s.push_next_page(string_data);
storage_.emplace<1>(this, logger_.get());
std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
good_ = std::get(storage_).good_;
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
byid_ = decltype(byid_)(&std::get(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
header_.reset(new spf_header(this, &logger_.get()));
}
file::file(void* data, int length, ::logger& log)
: logger_(log)
, schema_(nullptr)
, ifcroot_type_(nullptr)
, max_id_(0)
{
file_reader s(std::string((char*)data, length), caller_fed_tag{});
storage_.emplace<1>(this, logger_.get());
std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
good_ = std::get(storage_).good_;
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
byid_ = decltype(byid_)(&std::get(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
header_.reset(new spf_header(this, &logger_.get()));
}
file::file(const ifcopenshell::schema_definition* schema, filetype ty, const std::string& path, ::logger& log)
: logger_(log)
, schema_(schema)
, ifcroot_type_(schema_->declaration_by_name("IfcRoot"))
, max_id_(0)
{
if (ty == FT_AUTODETECT) {
ty = guess_file_type(path);
}
if (ty == FT_IFCSPF) {
storage_.emplace<1>(this, logger_.get());
byid_ = decltype(byid_)(&std::get(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
// byidentity_ = decltype(byidentity_)(&std::get(storage_).byidentity_);
} else if (ty == FT_ROCKSDB) {
storage_.emplace<2>(path, this);
byid_ = decltype(byid_)(&std::get(storage_).instance_by_name_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
// byidentity_ = decltype(byidentity_)(&std::get(storage_).instance_cache_);
} else {
throw std::runtime_error("Unsupported file format");
}
header_.reset(new spf_header(this, &logger_.get()));
set_default_header_values();
}
namespace {
template
void read_terminal(spf_lexer& lexer, const std::string& term, bool trailing_semicolon) {
if (lexer.next().as_string() != term) {
throw exception(std::string("Expected " + term));
}
if (trailing_semicolon) {
if (!lexer.next().is_operator(';')) {
throw exception("Expected ;");
}
}
}
template
shared_pointer_type read_header_entity(
ifcopenshell::file* file,
ifcopenshell::impl::in_memory_file_storage& storage,
spf_lexer& lexer,
ifcopenshell::unresolved_references& references_to_resolve,
const ifcopenshell::entity& decl) {
lexer.next();
storage.file = file;
storage.references_to_resolve = &references_to_resolve;
return storage.load(&lexer, std::nullopt, &decl, nullptr, -1);
}
template
void parse_header(
ifcopenshell::spf_header& header,
ifcopenshell::impl::in_memory_file_storage& storage,
spf_lexer& lexer,
ifcopenshell::unresolved_references& references_to_resolve) {
static const char* const ISO_10303_21 = "ISO-10303-21";
static const char* const HEADER = "HEADER";
read_terminal(lexer, ISO_10303_21, true);
read_terminal(lexer, HEADER, true);
read_terminal(lexer, Header_section_schema::file_description::Class().name_uc(), false);
header.set_file_description(read_header_entity(header.owner_file(), storage, lexer, references_to_resolve, Header_section_schema::file_description::Class()));
if (!lexer.next().is_operator(';')) {
throw exception("Expected ;");
}
read_terminal(lexer, Header_section_schema::file_name::Class().name_uc(), false);
header.set_file_name(read_header_entity(header.owner_file(), storage, lexer, references_to_resolve, Header_section_schema::file_name::Class()));
if (!lexer.next().is_operator(';')) {
throw exception("Expected ;");
}
read_terminal(lexer, Header_section_schema::file_schema::Class().name_uc(), false);
header.set_file_schema(read_header_entity(header.owner_file(), storage, lexer, references_to_resolve, Header_section_schema::file_schema::Class()));
if (!lexer.next().is_operator(';')) {
throw exception("Expected ;");
}
}
template
bool try_parse_header(
ifcopenshell::spf_header& header,
ifcopenshell::impl::in_memory_file_storage& storage,
spf_lexer& lexer,
ifcopenshell::unresolved_references& references_to_resolve) {
try {
parse_header(header, storage, lexer, references_to_resolve);
return true;
} catch (const std::exception& e) {
storage.logger_.get().error(e);
return false;
}
}
} // namespace
template
spf_header& ifcopenshell::instance_streamer::ensure_header() {
if (header_) {
return *header_;
}
if (owner_ != nullptr) {
header_ = &owner_->header();
header_->owner_file(owner_);
} else {
owned_header_ = std::make_unique