Files
IfcOpenShell/src/ifcparse/parse.cpp
T

3557 lines
141 KiB
C++

/********************************************************************************
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#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 <algorithm>
#include <boost/algorithm/string.hpp>
#include <boost/variant.hpp>
#include <boost/math/special_functions/fpclassify.hpp>
#include <ctime>
#include <set>
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include <string>
#include <iomanip>
#include <charconv>
#include <type_traits>
// 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 <xlocale.h>
#include <locale.h>
#endif
#ifdef USE_MMAP
#include <boost/filesystem/path.hpp>
#endif
#define PERMISSIVE_FLOAT
using namespace ifcopenshell;
template <typename Reader>
spf_lexer<Reader>::spf_lexer(Reader* stream_, ::logger& log)
: decoder_(nullptr)
, logger_(log) {
stream = stream_;
decoder_ = new character_decoder<Reader>(stream_, logger_);
}
template <typename Reader>
spf_lexer<Reader>::~spf_lexer() {
delete decoder_;
}
template <typename Reader>
size_t spf_lexer<Reader>::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 <typename Reader>
size_t spf_lexer<Reader>::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 <typename Reader>
std::string& spf_lexer<Reader>::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::array<std::string, 16>>());
}
// 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 <typename T>
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<T>) {
#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<T>(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 <bool IncludeDot = true>
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 <bool IncludeDot = true>
inline uint32_t has_special_char(uint32_t x) {
return eq_mask(x, static_cast<uint32_t>(chars::lpar)) |
eq_mask(x, static_cast<uint32_t>(chars::rpar)) |
eq_mask(x, static_cast<uint32_t>(chars::eq)) |
eq_mask(x, static_cast<uint32_t>(chars::comma)) |
eq_mask(x, static_cast<uint32_t>(chars::semi)) |
eq_mask(x, static_cast<uint32_t>(chars::slash)) |
eq_mask(x, static_cast<uint32_t>(chars::space)) |
eq_mask(x, static_cast<uint32_t>(chars::cr)) |
eq_mask(x, static_cast<uint32_t>(chars::lf)) |
eq_mask(x, static_cast<uint32_t>(chars::tab)) |
eq_mask(x, static_cast<uint32_t>(chars::quote)) |
(IncludeDot ? eq_mask(x, static_cast<uint32_t>(chars::dot)) : uint32_t{0});
}
}
//
// Returns the offset of the current token and moves cursor to next
//
template <typename Reader>
token spf_lexer<Reader>::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<false>(x) : SWAR::has_special_char<true>(x)) == 0) {
str.append(reinterpret_cast<const char*>(&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<false>(x) : SWAR::has_special_char<true>(x)) == 0) {
str.append(reinterpret_cast<const char*>(&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<file_reader<full_buffer_impl>>;
template class IFC_PARSE_API ifcopenshell::spf_lexer<file_reader<paged_file_impl>>;
template class IFC_PARSE_API ifcopenshell::spf_lexer<file_reader<pushed_sequential_impl>>;
#ifdef USE_MMAP
template class IFC_PARSE_API ifcopenshell::spf_lexer<file_reader<mmap_impl>>;
#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<typename Variant, typename T>
struct is_type_in_variant;
template<typename T, typename First, typename... Rest>
struct is_type_in_variant<std::variant<First, Rest...>, T>
{
static constexpr bool value = std::is_same<T, First>::value || is_type_in_variant<std::variant<Rest...>, T>::value;
};
template<typename T, typename Last>
struct is_type_in_variant<std::variant<Last>, T>
{
static constexpr bool value = std::is_same<T, Last>::value;
};
template<typename Variant, typename T>
constexpr bool is_type_in_variant_v = is_type_in_variant<Variant, T>::value;
class parameter_type_view {
const ifcopenshell::declaration* declaration_;
const std::vector<const ifcopenshell::attribute*>* attributes_;
std::unique_ptr<ifcopenshell::named_type> 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<ifcopenshell::declaration*>(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<size_t> 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 <typename Fn>
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<int64_t>,
std::vector<double>,
std::vector<std::string>,
std::vector<boost::dynamic_bitset<>>,
std::vector<ifcopenshell::reference_or_simple_type>,
std::vector<std::vector<int64_t>>,
std::vector<std::vector<double>>,
std::vector<std::vector<ifcopenshell::reference_or_simple_type>>
> 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 <typename T>
void append(const T& value) {
++values;
if constexpr (is_type_in_variant_v<direct_aggregate_storage, std::vector<std::decay_t<T>>>) {
if constexpr (
std::is_same_v<std::decay_t<T>, std::vector<int64_t>> ||
std::is_same_v<std::decay_t<T>, std::vector<double>> ||
std::is_same_v<std::decay_t<T>, std::vector<ifcopenshell::reference_or_simple_type>>
) {
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<std::decay_t<T>>{value};
} else if (auto* vector = std::get_if<std::vector<std::decay_t<T>>>(&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<std::vector<std::vector<int64_t>>>(&storage)) {
int_vector->emplace_back();
} else if (auto* double_vector = std::get_if<std::vector<std::vector<double>>>(&storage)) {
double_vector->emplace_back();
} else if (auto* reference_vector = std::get_if<std::vector<std::vector<ifcopenshell::reference_or_simple_type>>>(&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 <typename T>
void append_promoted(const T& value) {
if constexpr (std::is_same_v<std::decay_t<T>, int64_t>) {
if (auto* vector = std::get_if<std::vector<double>>(&storage)) {
vector->push_back((double) value);
return;
}
}
if constexpr (std::is_same_v<std::decay_t<T>, double>) {
if (auto* vector = std::get_if<std::vector<int64_t>>(&storage)) {
std::vector<double> promoted(vector->begin(), vector->end());
promoted.push_back(value);
storage = std::move(promoted);
return;
}
}
if constexpr (std::is_same_v<std::decay_t<T>, std::vector<int64_t>>) {
if (storage.index() == 0) {
std::vector<std::vector<int64_t>> promoted(pending_empty_aggregates);
pending_empty_aggregates = 0;
promoted.push_back(value);
storage = std::move(promoted);
return;
}
if (auto* vector = std::get_if<std::vector<std::vector<int64_t>>>(&storage)) {
vector->push_back(value);
return;
}
if (auto* vector = std::get_if<std::vector<std::vector<double>>>(&storage)) {
std::vector<double> promoted(value.begin(), value.end());
vector->push_back(std::move(promoted));
return;
}
}
if constexpr (std::is_same_v<std::decay_t<T>, std::vector<double>>) {
if (storage.index() == 0) {
std::vector<std::vector<double>> promoted(pending_empty_aggregates);
pending_empty_aggregates = 0;
promoted.push_back(value);
storage = std::move(promoted);
return;
}
if (auto* vector = std::get_if<std::vector<std::vector<double>>>(&storage)) {
vector->push_back(value);
return;
}
if (auto* vector = std::get_if<std::vector<std::vector<int64_t>>>(&storage)) {
std::vector<std::vector<double>> 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::decay_t<T>, std::vector<ifcopenshell::reference_or_simple_type>>) {
if (storage.index() == 0) {
std::vector<std::vector<ifcopenshell::reference_or_simple_type>> promoted(pending_empty_aggregates);
pending_empty_aggregates = 0;
promoted.push_back(value);
storage = std::move(promoted);
return;
}
if (auto* vector = std::get_if<std::vector<std::vector<ifcopenshell::reference_or_simple_type>>>(&storage)) {
vector->push_back(value);
return;
}
}
auto current = std::visit([](auto v) {
if constexpr (!std::is_same_v<decltype(v), blank>) {
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<int64_t>{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_DOUBLE) {
target.storage = std::vector<double>{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_STRING) {
target.storage = std::vector<std::string>{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_BINARY) {
target.storage = std::vector<boost::dynamic_bitset<>>{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE) {
target.storage = std::vector<ifcopenshell::reference_or_simple_type>{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_INT) {
target.storage = std::vector<std::vector<int64_t>>{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_DOUBLE) {
target.storage = std::vector<std::vector<double>>{};
} else if (argument_type == ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE) {
target.storage = std::vector<std::vector<ifcopenshell::reference_or_simple_type>>{};
} else {
target.append_empty_nested();
}
}
template <typename T>
void set_direct_attribute(
in_memory_attribute_storage& storage,
std::optional<size_t> 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<std::decay_t<T>, 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::decay_t<T>, std::vector<ifcopenshell::reference_or_simple_type>>) {
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::decay_t<T>, std::vector<std::vector<ifcopenshell::reference_or_simple_type>>>) {
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 <typename Reader>
void skip_aggregate(ifcopenshell::spf_lexer<Reader>* 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 <typename Reader>
direct_aggregate read_direct_aggregate(
ifcopenshell::impl::in_memory_file_storage& storage,
ifcopenshell::spf_lexer<Reader>* tokens,
std::optional<size_t> 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<std::decay_t<decltype(value)>, 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 <typename Reader>
shared_pointer_type ifcopenshell::impl::in_memory_file_storage::load(
ifcopenshell::spf_lexer<Reader>* tokens,
std::optional<size_t> entity_instance_name,
const ifcopenshell::declaration* declaration,
const ifcopenshell::entity* entity,
int attribute_index,
bool coerce_attribute_count
) {
static_cast<void>(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<std::decay_t<decltype(value)>, 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<instance_data>(file, declaration, (declaration && declaration->as_entity()) ? (uint32_t)entity_instance_name.value_or(0) : 0, std::move(storage));
}
template <typename Reader>
void ifcopenshell::impl::in_memory_file_storage::try_read_semicolon(ifcopenshell::spf_lexer<Reader>* 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 <typename T>
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, &current);
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<uint32_t> 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, &current);
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<size_t> 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<void> {
private:
StringBuilderVisitor(const StringBuilderVisitor&); //N/A
StringBuilderVisitor& operator=(const StringBuilderVisitor&); //N/A
std::ostream& data_;
template <typename T>
void serialize(const std::vector<T>& i) {
data_ << "(";
for (typename std::vector<T>::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<std::string>(character_encoder(s));
} else {
data_ << '\'' << s << '\'';
}
}
void operator()(const std::vector<int64_t>& i);
void operator()(const std::vector<double>& i);
void operator()(const std::vector<std::string>& i);
void operator()(const std::vector<boost::dynamic_bitset<>>& 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<express::Base>& i) {
data_ << "(";
for (auto it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
(*this)(*it);
}
data_ << ")";
}
void operator()(const std::vector<std::vector<int64_t>>& i);
void operator()(const std::vector<std::vector<double>>& i);
void operator()(const std::vector<std::vector<express::Base>>& 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<std::string>& i) {
data_ << "(";
for (std::vector<std::string>::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<double>& i) {
data_ << "(";
for (std::vector<double>::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<boost::dynamic_bitset<>>& i) {
data_ << "(";
for (std::vector<boost::dynamic_bitset<>>::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<int64_t>& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector<double>& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector<std::string>& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector<boost::dynamic_bitset<>>& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector<std::vector<int64_t>>& i) {
data_ << "(";
for (std::vector<std::vector<int64_t>>::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
serialize(*it);
}
data_ << ")";
}
void StringBuilderVisitor::operator()(const std::vector<std::vector<double>>& i) {
data_ << "(";
for (std::vector<std::vector<double>>::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<blank>(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<unsigned>& 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<express::Base>* list_;
public:
add_to_instance_list_visitor(std::vector<express::Base>* list)
: list_(list) {}
void operator()(const express::Base& inst) {
list_->push_back(inst);
}
};
class apply_individual_instance_visitor {
private:
std::optional<attribute_value> attribute_;
int attribute_index_;
const express::Base inst_;
template <typename T>
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<express::Base> 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<std::vector<express::Base>> 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 <typename T>
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 T>
typename std::enable_if<
(!std::is_base_of_v<express::Base, T> || std::is_same_v<express::Base, T>),
void>::type
express::Base::set_attribute_value(size_t i, const T& t) {
if constexpr (std::is_same_v<std::decay_t<T>, double>) {
if (!std::isfinite(t)) {
throw ifcopenshell::exception("Only finite values are allowed");
}
}
if constexpr (std::is_same_v<std::decay_t<T>, std::vector<double>>) {
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::decay_t<T>, std::vector<std::vector<double>>>) {
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<const std::string, express::Base>& 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<T, express::Base> || std::is_same_v<T, std::vector<express::Base>> || std::is_same_v<T, std::vector<std::vector<express::Base>>> || std::is_same_v<T, blank>) {
// 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<T, express::Base> || std::is_same_v<T, std::vector<express::Base>> || std::is_same_v<T, std::vector<std::vector<express::Base>>>) {
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 T>
typename std::enable_if<
(!std::is_base_of_v<express::Base, T> || std::is_same_v<express::Base, T>),
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<mmap_impl> s(fn);
storage_.emplace<1>(this, logger_.get());
std::get<impl::in_memory_file_storage>(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
} else {
file_reader<full_buffer_impl> s(fn);
storage_.emplace<1>(this, logger_.get());
std::get<impl::in_memory_file_storage>(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
}
if ((good_ = std::get<impl::in_memory_file_storage>(storage_).good_)) {
// @todo unify these names, it's already confusing enough as it stands
byid_ = decltype(byid_)(&std::get<impl::in_memory_file_storage>(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get<impl::in_memory_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::in_memory_file_storage>(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<full_buffer_impl> s(path);
storage_.emplace<1>(this, logger_.get());
std::get<impl::in_memory_file_storage>(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
if ((good_ = std::get<impl::in_memory_file_storage>(storage_).good_)) {
// @todo unify these names, it's already confusing enough as it stands
byid_ = decltype(byid_)(&std::get<impl::in_memory_file_storage>(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get<impl::in_memory_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::in_memory_file_storage>(storage_).byguid_);
}
// byidentity_ = decltype(byidentity_)(&std::get<impl::in_memory_file_storage>(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<impl::rocks_db_file_storage>(storage_).db == nullptr) {
storage_.emplace<0>();
good_ = file_open_status::READ_ERROR;
} else {
if (std::get<impl::rocks_db_file_storage>(storage_).read_schema(schema_)) {
byid_ = decltype(byid_)(&std::get<impl::rocks_db_file_storage>(storage_).instance_by_name_);
byref_excl_ = decltype(byref_excl_)(&std::get<impl::rocks_db_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::rocks_db_file_storage>(storage_).byguid_);
good_ = file_open_status::SUCCESS;
} else {
good_ = file_open_status::UNSUPPORTED_SCHEMA;
}
}
// byidentity_ = decltype(byidentity_)(&std::get<impl::rocks_db_file_storage>(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<pushed_sequential_impl> 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<impl::in_memory_file_storage>(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
good_ = std::get<impl::in_memory_file_storage>(storage_).good_;
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
byid_ = decltype(byid_)(&std::get<impl::in_memory_file_storage>(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get<impl::in_memory_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::in_memory_file_storage>(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<pushed_sequential_impl> s(std::string((char*)data, length), caller_fed_tag{});
storage_.emplace<1>(this, logger_.get());
std::get<impl::in_memory_file_storage>(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
good_ = std::get<impl::in_memory_file_storage>(storage_).good_;
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
byid_ = decltype(byid_)(&std::get<impl::in_memory_file_storage>(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get<impl::in_memory_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::in_memory_file_storage>(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<impl::in_memory_file_storage>(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get<impl::in_memory_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::in_memory_file_storage>(storage_).byguid_);
// byidentity_ = decltype(byidentity_)(&std::get<impl::in_memory_file_storage>(storage_).byidentity_);
} else if (ty == FT_ROCKSDB) {
storage_.emplace<2>(path, this);
byid_ = decltype(byid_)(&std::get<impl::rocks_db_file_storage>(storage_).instance_by_name_);
byref_excl_ = decltype(byref_excl_)(&std::get<impl::rocks_db_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::rocks_db_file_storage>(storage_).byguid_);
// byidentity_ = decltype(byidentity_)(&std::get<impl::rocks_db_file_storage>(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 <typename Reader>
void read_terminal(spf_lexer<Reader>& 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 <typename Reader>
shared_pointer_type read_header_entity(
ifcopenshell::file* file,
ifcopenshell::impl::in_memory_file_storage& storage,
spf_lexer<Reader>& 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 <typename Reader>
void parse_header(
ifcopenshell::spf_header& header,
ifcopenshell::impl::in_memory_file_storage& storage,
spf_lexer<Reader>& 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 <typename Reader>
bool try_parse_header(
ifcopenshell::spf_header& header,
ifcopenshell::impl::in_memory_file_storage& storage,
spf_lexer<Reader>& 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 <typename Reader>
spf_header& ifcopenshell::instance_streamer<Reader>::ensure_header() {
if (header_) {
return *header_;
}
if (owner_ != nullptr) {
header_ = &owner_->header();
header_->owner_file(owner_);
} else {
owned_header_ = std::make_unique<spf_header>(owner_, &logger_.get());
header_ = owned_header_.get();
}
return *header_;
}
template <typename Reader>
void ifcopenshell::instance_streamer<Reader>::initialize_header() {
storage_.file = owner_;
storage_.schema = schema_;
storage_.references_to_resolve = &references_to_resolve_;
if (!lexer_ || !stream_ || !stream_->size() || stream_->eof()) {
return;
}
auto& header = ensure_header();
if (try_parse_header(header, storage_, *lexer_, references_to_resolve_) && header.file_schema().schema_identifiers().size() == 1) {
try {
schema_ = ifcopenshell::schema_by_name(header.file_schema().schema_identifiers().front());
good_ = file_open_status::SUCCESS;
} catch (const ifcopenshell::exception&) {
}
}
storage_.schema = schema_;
types_to_bypass_materialized_.resize(schema_->declarations().size(), false);
for (auto& bp : types_to_bypass_) {
std::function<void(const ifcopenshell::entity*)> mark;
mark = [&](const ifcopenshell::entity* e) {
types_to_bypass_materialized_[e->index_in_schema()] = true;
for (auto& subtype : e->subtypes()) {
mark(subtype);
}
};
if (auto* e = bp->as_entity()) {
mark(e);
}
}
}
template <typename Reader>
bool ifcopenshell::instance_streamer<Reader>::has_semicolon() const {
auto local_stream = stream_->clone();
auto local_lexer = spf_lexer<Reader>(&local_stream, logger_.get());
token t;
try {
t = local_lexer.next();
} catch (const std::out_of_range&) {
return false;
}
while (t.type != token::Token_NONE) {
if (t.is_operator(';')) {
return true;
}
try {
t = local_lexer.next();
} catch (const std::out_of_range&) {
break;
}
}
return false;
}
template <typename Reader>
size_t ifcopenshell::instance_streamer<Reader>::semicolon_count() const {
auto local_stream = stream_->clone();
auto local_lexer = spf_lexer<Reader>(&local_stream, logger_.get());
token t;
size_t count = 0;
try {
t = local_lexer.next();
} catch (const std::out_of_range&) {
return false;
}
while (t.type != token::Token_NONE) {
if (t.is_operator(';')) {
count++;
}
try {
t = local_lexer.next();
} catch (const std::out_of_range&) {
break;
}
}
return count;
}
template <typename Reader>
void ifcopenshell::instance_streamer<Reader>::push_page(const std::string& page) {
stream_->push_next_page(page);
if (good_ == file_open_status::NO_HEADER) {
initialize_header();
}
}
template <typename Reader>
ifcopenshell::instance_streamer<Reader>::instance_streamer(ifcopenshell::file* f, ::logger& log)
: stream_(nullptr)
, header_(nullptr)
, owner_(f)
, token_stream_(3, token{})
, schema_(nullptr)
, storage_(nullptr, log)
, logger_(log)
, progress_(0)
{
if constexpr (std::is_same_v<Reader, file_reader<full_buffer_impl>>) {
owned_stream_ = std::make_unique<Reader>(caller_fed_tag{});
} else if constexpr (std::is_same_v<Reader, file_reader<pushed_sequential_impl>>) {
owned_stream_ = std::make_unique<Reader>(caller_fed_tag{});
} else {
static_assert(file_reader_dependent_false_v<Reader>, "Default instance_streamer requires a pushed sequential reader");
}
stream_ = owned_stream_.get();
lexer_ = std::make_unique<spf_lexer<Reader>>(stream_, logger_.get());
good_ = file_open_status::NO_HEADER;
storage_.file = f;
storage_.references_to_resolve = &references_to_resolve_;
}
template <typename Reader>
ifcopenshell::instance_streamer<Reader>::instance_streamer(const std::string& fn, bool mmap, ifcopenshell::file* f, ::logger& log)
: stream_(nullptr)
, header_(nullptr)
, owner_(f)
, token_stream_(3, token{})
, schema_(nullptr)
, storage_(nullptr, log)
, logger_(log)
, progress_(0)
{
if constexpr (std::is_same_v<Reader, file_reader<full_buffer_impl>>) {
(void)mmap;
owned_stream_ = std::make_unique<Reader>(fn);
#ifdef USE_MMAP
} else if constexpr (std::is_same_v<Reader, file_reader<mmap_impl>>) {
(void)mmap;
owned_stream_ = std::make_unique<Reader>(fn);
#endif
} else {
static_assert(file_reader_dependent_false_v<Reader>, "Path-based instance_streamer requires a file-backed reader");
}
stream_ = owned_stream_.get();
lexer_ = std::make_unique<spf_lexer<Reader>>(stream_, logger_.get());
good_ = file_open_status::NO_HEADER;
initialize_header();
}
template <typename Reader>
ifcopenshell::instance_streamer<Reader>::instance_streamer(void* data, int length, ifcopenshell::file* f, ::logger& log)
: stream_(nullptr)
, header_(nullptr)
, owner_(f)
, token_stream_(3, token{})
, schema_(nullptr)
, storage_(nullptr, log)
, logger_(log)
, progress_(0)
{
if constexpr (std::is_same_v<Reader, file_reader<full_buffer_impl>>) {
owned_stream_ = std::make_unique<Reader>(std::string((char*)data, length), caller_fed_tag{});
} else if constexpr (std::is_same_v<Reader, file_reader<pushed_sequential_impl>>) {
owned_stream_ = std::make_unique<Reader>(std::string((char*)data, length), caller_fed_tag{});
} else {
static_assert(file_reader_dependent_false_v<Reader>, "Buffer-based instance_streamer requires a pushed sequential reader");
}
stream_ = owned_stream_.get();
lexer_ = std::make_unique<spf_lexer<Reader>>(stream_, logger_.get());
good_ = file_open_status::NO_HEADER;
initialize_header();
}
template <typename Reader>
ifcopenshell::instance_streamer<Reader>::instance_streamer(Reader* stream, ifcopenshell::file* f, ::logger& log)
: stream_(stream)
, header_(nullptr)
, owner_(f)
, token_stream_(3, token{})
, schema_(nullptr)
, storage_(nullptr, log)
, logger_(log)
, progress_(0)
{
lexer_ = std::make_unique<spf_lexer<Reader>>(stream_, logger_.get());
good_ = file_open_status::NO_HEADER;
initialize_header();
}
template <typename Reader>
void ifcopenshell::instance_streamer<Reader>::bypass_types(const std::set<std::string>& type_names) {
for (auto& name : type_names) {
try {
types_to_bypass_.push_back(schema_->declaration_by_name(name));
} catch (const exception&) {
continue;
}
}
}
template <typename Reader>
std::optional<std::tuple<size_t, const ifcopenshell::declaration*, shared_pointer_type>> ifcopenshell::instance_streamer<Reader>::read_instance() {
std::optional<std::tuple<size_t, const ifcopenshell::declaration*, shared_pointer_type>> return_value;
if (yield_header_instances_ && header_ && yielded_header_instances_ < 3) {
if (yielded_header_instances_ == 0) {
return_value.emplace(
0,
&header_->file_description().declaration(),
#ifdef IFOPSH_SAFE_INSTANCE
header_->file_description().data_weak().lock());
#else
header_->file_description().data_weak());
#endif
} else if (yielded_header_instances_ == 1) {
return_value.emplace(
0,
&header_->file_name().declaration(),
#ifdef IFOPSH_SAFE_INSTANCE
header_->file_name().data_weak().lock());
#else
header_->file_name().data_weak());
#endif
} else if (yielded_header_instances_ == 2) {
return_value.emplace(
0,
&header_->file_schema().declaration(),
#ifdef IFOPSH_SAFE_INSTANCE
header_->file_schema().data_weak().lock());
#else
header_->file_schema().data_weak());
#endif
}
yielded_header_instances_ += 1;
return return_value;
}
unsigned current_id = 0;
while (good_ && !lexer_->stream->eof() && !current_id) {
if (token_stream_[0].type == ifcopenshell::token::Token_IDENTIFIER &&
token_stream_[1].type == ifcopenshell::token::Token_OPERATOR &&
token_stream_[1].value_char == '=' &&
token_stream_[2].type == ifcopenshell::token::Token_KEYWORD) {
current_id = token_stream_[0].as_identifier();
const ifcopenshell::declaration* entity_type;
try {
entity_type = schema_->declaration_by_name(token_stream_[2].as_string());
} catch (const exception& ex) {
logger_.get().message(::logger::LOG_ERROR, std::string(ex.what()) + " at offset " + std::to_string(token_stream_[2].start_pos));
current_id = 0;
goto advance;
}
if (entity_type->as_entity() == nullptr) {
logger_.get().message(::logger::LOG_ERROR, "Non-entity type " + entity_type->name() + " at offset " + std::to_string(token_stream_[2].start_pos));
current_id = 0;
goto advance;
}
if (types_to_bypass_materialized_[entity_type->index_in_schema()]) {
bypassed_instances_.push_back(current_id);
current_id = 0;
goto advance;
}
lexer_->next();
try {
auto data = storage_.load(lexer_.get(), current_id, entity_type, entity_type->as_entity(), -1, coerce_attribute_count);
if (((++progress_) % 1000) == 0) {
std::stringstream ss;
ss << "\r#" << current_id;
logger_.get().status(ss.str(), false);
}
return_value.emplace(
(size_t)current_id,
entity_type,
data);
} catch (const invalid_token_exception& e) {
good_ = file_open_status::INVALID_SYNTAX;
logger_.get().error(e);
break;
}
}
advance:
token next_token;
try {
next_token = lexer_->next();
} catch (const exception& e) {
logger_.get().message(::logger::LOG_ERROR, std::string(e.what()) + ". Parsing terminated");
} catch (...) {
logger_.get().message(::logger::LOG_ERROR, "Parsing terminated");
}
if (!lexer_->stream->eof() && !next_token) {
good_ = file_open_status::INVALID_SYNTAX;
break;
}
token_stream_.push_back(next_token);
}
stream_->drop_pages();
lexer_->reset_pool();
return return_value;
}
template class IFC_PARSE_API ifcopenshell::instance_streamer<file_reader<full_buffer_impl>>;
template <typename Reader>
void ifcopenshell::impl::in_memory_file_storage::read_from_stream(Reader* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set<std::string>& typed_to_bypass) {
schema = nullptr;
if (!s->size() || s->eof()) {
good_ = file_open_status::READ_ERROR;
return;
}
std::vector<std::string> schemas;
instance_streamer<Reader> streamer(s, file, logger_.get());
streamer.yield_header_instances(false);
if (const auto* header = streamer.header()) {
try {
schemas = header->file_schema().schema_identifiers();
} catch (...) {
}
}
schema = streamer.schema();
if (schema == nullptr && schemas.size() == 1) {
try {
schema = ifcopenshell::schema_by_name(schemas.front());
} catch (const ifcopenshell::exception& e) {
good_ = file_open_status::UNSUPPORTED_SCHEMA;
logger_.get().error(e);
}
}
if (schema == nullptr) {
if (schemas.empty()) {
good_ = streamer.status();
} else {
good_ = file_open_status::UNSUPPORTED_SCHEMA;
}
logger_.get().message(::logger::LOG_ERROR, "No support for file schema encountered (" + boost::algorithm::join(schemas, ", ") + ")");
return;
}
auto ifcroot_type_ = schema->declaration_by_name("IfcRoot");
streamer.bypass_types(typed_to_bypass);
logger_.get().status("Scanning file...");
while (streamer) {
auto inst = streamer.read_instance();
if (!inst) {
break;
}
auto current_id = std::get<0>(*inst);
express::Base instance(std::get<2>(*inst));
if (instance.declaration().is(*ifcroot_type_)) {
try {
const std::string guid = instance.get_attribute_value(0);
if (byguid_.find(guid) != byguid_.end()) {
std::stringstream ss;
ss << "Instance encountered with non-unique GlobalId " << guid;
logger_.get().message(::logger::LOG_WARNING, ss.str());
}
byguid_[guid] = instance;
} catch (const exception& ex) {
logger_.get().message(::logger::LOG_ERROR, ex.what());
}
}
const ifcopenshell::declaration* ty = &instance.declaration();
bytype_excl_[ty].push_back(instance);
if (byid_.find(current_id) != byid_.end()) {
std::stringstream ss;
ss << "Overwriting instance with name #" << current_id;
logger_.get().message(::logger::LOG_WARNING, ss.str());
}
byid_.insert({(uint32_t)current_id, std::get<2>(*inst)});
max_id = (std::max)(max_id, (unsigned int)current_id);
}
good_ = streamer.status();
byref_excl_ = std::move(streamer.inverses());
byref_excl_.sort();
read_simple_type_instances = streamer.steal_instances();
logger_.get().status("\rDone scanning file ");
if (good_ != file_open_status::SUCCESS) {
return;
}
const auto& bypassed = streamer.bypassed_instances();
for (const auto& p : streamer.references()) {
const auto& ref = p.first.name_;
const auto& refattr = p.first.index_;
auto owner_it = byid_.find(ref);
if (owner_it == byid_.end()) {
logger_.get().error("Instance #" + std::to_string(ref) + " referenced at attribute index " + std::to_string(refattr) + " not found");
continue;
}
auto& owner = owner_it->second;
if (auto* v = std::get_if<reference_or_simple_type>(&p.second)) {
if (auto* name = std::get_if<instance_reference>(v)) {
if (std::binary_search(bypassed.begin(), bypassed.end(), *name)) {
continue;
}
auto it = byid_.find(*name);
if (it == byid_.end()) {
logger_.get().error("Instance reference #" + std::to_string(*name) + " used by instance #" + std::to_string(ref) + " at attribute index " + std::to_string(refattr) + " not found at offset " + std::to_string(name->file_offset));
} else {
auto& storage = owner;
auto attr_index = p.first.index_;
if (storage->template has_attribute_value<express::Base>(attr_index)) {
express::Base inst = storage->get_attribute_value(attr_index);
if (inst && !inst.declaration().as_entity()) {
// Probably a case of IfcPropertySetDefinitionSet, divert storage of reference to the simply type instance
#ifdef IFOPSH_SAFE_INSTANCE
storage = inst.data_weak().lock();
#else
storage = inst.data_weak();
#endif
attr_index = 0;
}
}
if (storage->template has_attribute_value<blank>(attr_index)) {
storage->set_attribute_value(attr_index, express::Base(it->second));
} else {
logger_.get().error("Duplicate definition for instance reference");
}
}
} else if (auto inst = std::get_if<express::Base>(v)) {
owner->set_attribute_value(p.first.index_, *inst);
}
} else if (auto* vv = std::get_if<std::vector<reference_or_simple_type>>(&p.second)) {
std::vector<express::Base> instances;
instances.reserve(vv->size());
for (const auto& vi : *vv) {
if (auto* name = std::get_if<instance_reference>(&vi)) {
if (std::binary_search(bypassed.begin(), bypassed.end(), *name)) {
continue;
}
auto it = byid_.find(*name);
if (it == byid_.end()) {
logger_.get().error("Instance reference #" + std::to_string(*name) + " used by instance #" + std::to_string(ref) + " at attribute index " + std::to_string(refattr) + " not found at offset " + std::to_string(name->file_offset));
} else {
instances.push_back(express::Base(it->second));
}
} else if (auto* inst = std::get_if<express::Base>(&vi)) {
instances.push_back(*inst);
}
}
auto& storage = owner;
auto attr_index = p.first.index_;
if (storage->template has_attribute_value<express::Base>(attr_index)) {
express::Base inst = storage->get_attribute_value(attr_index);
if (inst && !inst.declaration().as_entity()) {
// Probably a case of IfcPropertySetDefinitionSet, divert storage of reference to the simply type instance
#ifdef IFOPSH_SAFE_INSTANCE
storage = inst.data_weak().lock();
#else
storage = inst.data_weak();
#endif
attr_index = 0;
}
}
if (storage->template has_attribute_value<blank>(attr_index)) {
storage->set_attribute_value(attr_index, instances);
} else {
logger_.get().error("Duplicate definition for instance reference");
}
} else if (auto* vvv = std::get_if<std::vector<std::vector<reference_or_simple_type>>>(&p.second)) {
std::vector<std::vector<express::Base>> instances;
for (const auto& vi : *vvv) {
auto& inner = instances.emplace_back();
for (const auto& vii : vi) {
if (auto* name = std::get_if<instance_reference>(&vii)) {
if (std::binary_search(bypassed.begin(), bypassed.end(), *name)) {
continue;
}
auto it = byid_.find(*name);
if (it == byid_.end()) {
logger_.get().error("Instance reference #" + std::to_string(*name) + " used by instance #" + std::to_string(ref) + " at attribute index " + std::to_string(refattr) + " not found at offset " + std::to_string(name->file_offset));
} else {
inner.push_back(express::Base(it->second));
}
} else if (auto* inst = std::get_if<express::Base>(&vii)) {
inner.push_back(*inst);
}
}
}
auto& storage = owner;
auto attr_index = p.first.index_;
if (storage->template has_attribute_value<express::Base>(attr_index)) {
express::Base inst = storage->get_attribute_value(attr_index);
if (inst && !inst.declaration().as_entity()) {
// Probably a case of IfcPropertySetDefinitionSet, divert storage of reference to the simply type instance
#ifdef IFOPSH_SAFE_INSTANCE
storage = inst.data_weak().lock();
#else
storage = inst.data_weak();
#endif
attr_index = 0;
}
}
if (storage->template has_attribute_value<blank>(attr_index)) {
storage->set_attribute_value(attr_index, instances);
} else {
logger_.get().error("Duplicate definition for instance reference");
}
}
}
logger_.get().status("Done resolving references");
}
template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(file_reader<full_buffer_impl>* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set<std::string>& typed_to_bypass);
template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(file_reader<pushed_sequential_impl>* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set<std::string>& typed_to_bypass);
#ifdef USE_MMAP
template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(file_reader<mmap_impl>* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set<std::string>& typed_to_bypass);
#endif
void file::recalculate_id_counter() {
/*
// @todo
entity_by_id_t::key_type k = 0;
for (auto& p : byid_) {
if (p.first > k) {
k = p.first;
}
}
max_id_ = (unsigned int)k;
*/
}
class traversal_recorder {
std::vector<express::Base> list_;
std::map<int, std::vector<express::Base>> instances_by_level_;
int mode_;
public:
traversal_recorder(int mode) : mode_(mode) {
};
void push_back(int level, const express::Base& instance) {
if (mode_ == 0) {
list_.push_back(instance);
} else {
auto& l = instances_by_level_[level];
l.push_back(instance);
}
}
std::vector<express::Base> get_list() const {
if (mode_ == 0) {
return list_;
}
std::vector<express::Base> l;
for (const auto& p : instances_by_level_) {
l.insert(l.end(), p.second.begin(), p.second.end());
}
return l;
}
};
class traversal_visitor {
private:
std::set<express::Base>& visited_;
traversal_recorder& list_;
int level_;
int max_level_;
public:
traversal_visitor(std::set<express::Base>& visited, traversal_recorder& list, int level, int max_level)
: visited_(visited),
list_(list),
level_(level),
max_level_(max_level) {}
void operator()(const express::Base& inst, int index);
};
void traverse_(const express::Base& instance, std::set<express::Base>& visited, traversal_recorder& list, int level, int max_level) {
if (visited.find(instance) != visited.end()) {
return;
}
visited.insert(instance);
list.push_back(level, instance);
if (level >= max_level && max_level > 0) {
return;
}
traversal_visitor visit(visited, list, level + 1, max_level);
apply_individual_instance_visitor(instance).apply(visit);
}
void traversal_visitor::operator()(const express::Base& inst, int /* index */) {
traverse_(inst, visited_, list_, level_, max_level_);
}
std::vector<express::Base> ifcopenshell::traverse(const express::Base& instance, int max_level) {
std::set<express::Base> visited;
traversal_recorder recorder(0);
traverse_(instance, visited, recorder, 0, max_level);
return recorder.get_list();
}
// I'm cheating this isn't breadth-first, but rather we record visited instances
// keeping track of their rank and return a list ordered by rank. Is this equivalent?
std::vector<express::Base> ifcopenshell::traverse_breadth_first(const express::Base& instance, int max_level) {
std::set<express::Base> visited;
traversal_recorder recorder(1);
traverse_(instance, visited, recorder, 0, max_level);
return recorder.get_list();
}
/// @note: for backwards compatibility
std::vector<express::Base> file::traverse(const express::Base& instance, int max_level) {
return ifcopenshell::traverse(instance, max_level);
}
/// @note: for backwards compatibility
std::vector<express::Base> file::traverse_breadth_first(const express::Base& instance, int max_level) {
return ifcopenshell::traverse_breadth_first(instance, max_level);
}
express::Base file::add_entity(const express::Base& entity, int id) {
if (entity.file() == this) {
return entity;
}
if (entity.declaration().schema() != schema()) {
throw ifcopenshell::exception("Unabled to add instance from " + entity.declaration().schema()->name() + " schema to file with " + schema()->name() + " schema");
}
// If this instance has been inserted before, return
// a reference to the copy that was created from it.
entity_entity_map_t::iterator mit = entity_file_map_.find(entity.identity());
if (mit != entity_file_map_.end()) {
return mit->second;
}
// Obtain all forward references by a depth-first
// traversal and add them to the file.
try {
auto entity_attributes = traverse(entity, 1);
for (auto it = entity_attributes.begin() + 1; it != entity_attributes.end(); ++it) {
if (*it != entity) {
entity_entity_map_t::iterator mit2 = entity_file_map_.find(it->identity());
if (mit2 == entity_file_map_.end()) {
entity_file_map_.insert(entity_entity_map_t::value_type(it->identity(), add_entity(*it)));
}
}
}
} catch (...) {
logger_.get().message(::logger::LOG_ERROR, "Failed to visit forward references of", entity);
}
// An instance is being added from another file. A copy of the
// container and entity is created. The attribute references
// need to be updated to point to instances in this file.
file* other_file = entity.file();
auto new_entity = create(&entity.declaration(), id);
auto* decl = &entity.declaration();
auto num_attributes = (decl->as_entity() ? decl->as_entity()->attribute_count() : 1);
for (size_t i = 0; i < num_attributes; ++i) {
entity.get_attribute_value(i).apply_visitor([this, i, decl, &new_entity](const auto& v) {
using U = std::decay_t<decltype(v)>;
// only need to copy non-instance attribute values, others are assigned below after mapping
if constexpr (std::is_same_v<U, express::Base>) {
} else if constexpr (std::is_same_v<U, std::vector<express::Base>>) {
} else if constexpr (std::is_same_v<U, std::vector<std::vector<express::Base>>>) {
} else {
new_entity.set_attribute_value(i, v);
}
});
}
// In case an entity is added that contains geometry, the unit
// information needs to be accounted for for IfcLengthMeasures.
double conversion_factor = calculate_unit_factors ? std::numeric_limits<double>::quiet_NaN() : 1.0;
for (size_t i = 0; i < num_attributes; ++i) {
// old attribute value
auto attr = entity.get_attribute_value(i);
ifcopenshell::argument_type attr_type = attr.type();
ifcopenshell::declaration* potentially_length_measure_decl = 0;
if (decl->as_entity() != nullptr) {
potentially_length_measure_decl = 0;
const parameter_type* pt = decl->as_entity()->attribute_by_index(i)->type_of_attribute();
while (pt->as_aggregation_type() != nullptr) {
pt = pt->as_aggregation_type()->type_of_element();
}
if (pt->as_named_type() != nullptr) {
potentially_length_measure_decl = pt->as_named_type()->declared_type();
}
}
if (attr_type == ifcopenshell::Argument_ENTITY_INSTANCE) {
entity_entity_map_t::const_iterator eit = entity_file_map_.find(((express::Base)(attr)).identity());
if (eit == entity_file_map_.end()) {
throw ifcopenshell::exception("Unable to map instance to file");
}
// @todo previously, we directly use storage::set() not to trigger inverse recalculation which happens at the end
new_entity.set_attribute_value(i, eit->second);
} else if (attr_type == ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE) {
std::vector<express::Base> instances = attr;
std::vector<express::Base> new_instances;
for (auto& i : instances) {
entity_entity_map_t::const_iterator eit = entity_file_map_.find(i.identity());
if (eit == entity_file_map_.end()) {
throw ifcopenshell::exception("Unable to map instance to file");
}
new_instances.push_back(eit->second);
}
new_entity.set_attribute_value(i, new_instances);
} else if (attr_type == ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE) {
std::vector<std::vector<express::Base>> instances = attr;
std::vector<std::vector<express::Base>> new_instances;
for (auto& v : instances) {
new_instances.emplace_back();
for (auto& i : v) {
entity_entity_map_t::const_iterator eit = entity_file_map_.find(i.identity());
if (eit == entity_file_map_.end()) {
throw ifcopenshell::exception("Unable to map instance to file");
}
new_instances.back().push_back(eit->second);
}
}
new_entity.set_attribute_value(i, new_instances);
} else if ((potentially_length_measure_decl != nullptr) && potentially_length_measure_decl->is(*schema()->declaration_by_name("IfcLengthMeasure"))) {
if (boost::math::isnan(conversion_factor)) {
std::pair<express::Base, double> this_file_unit = {express::Base{}, 1.0};
std::pair<express::Base, double> other_file_unit = {express::Base{}, 1.0};
try {
this_file_unit = get_unit("LENGTHUNIT");
other_file_unit = other_file->get_unit("LENGTHUNIT");
} catch (ifcopenshell::exception&) {
}
if (this_file_unit.first && other_file_unit.first) {
conversion_factor = other_file_unit.second / this_file_unit.second;
} else {
conversion_factor = 1.;
}
}
if (attr_type == ifcopenshell::Argument_DOUBLE) {
double v = attr;
v *= conversion_factor;
new_entity.set_attribute_value(i, v);
} else if (attr_type == ifcopenshell::Argument_AGGREGATE_OF_DOUBLE) {
std::vector<double> v = attr;
for (std::vector<double>::iterator it = v.begin(); it != v.end(); ++it) {
(*it) *= conversion_factor;
}
new_entity.set_attribute_value(i, v);
} else if (attr_type == ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_DOUBLE) {
std::vector<std::vector<double>> v = attr;
for (std::vector<std::vector<double>>::iterator it = v.begin(); it != v.end(); ++it) {
std::vector<double>& v2 = (*it);
for (std::vector<double>::iterator jt = v2.begin(); jt != v2.end(); ++jt) {
(*jt) *= conversion_factor;
}
}
new_entity.set_attribute_value(i, v);
}
}
}
entity_file_map_.insert(entity_entity_map_t::value_type(entity.identity(), new_entity));
// For subtypes of IfcRoot, the GUID mapping needs to be updated.
/*
if (decl->is(*ifcroot_type_)) {
try {
const std::string guid = new_entity.get_attribute_value(0);
if (byguid_.find(guid) != byguid_.end()) {
std::stringstream ss;
ss << "Overwriting entity with guid " << guid;
logger_.get().message(::logger::LOG_WARNING, ss.str());
}
byguid_.insert({ guid, new_entity });
} catch (const std::exception& ex) {
logger_.get().message(::logger::LOG_ERROR, ex.what());
}
}
*/
// add_type_ref(new_entity);
return new_entity;
}
void file::remove_entity(const express::Base& entity) {
auto id = entity.id();
auto file_entity = instance_by_id(id);
// Attention when running remove_entity inside a loop over a list of entities to be removed.
// This invalidates the iterator. A workaround is to reverse the loop:
// boost::shared_ptr<aggregate_of_instance> entities = ...;
// for (auto it = entities->end() - 1; it >= entities->begin(); --it) {
// express::Base *const inst = *it;
// model->remove_entity(inst);
// }
// TODO: Create a set of weak relations. Inverse relations that do not dictate an
// instance to be retained. For example: when deleting an IfcRepresentation, the
// individual IfcRepresentationItems can not be deleted if an IfcStyledItem is
// related. Hence, the IfcRepresentationItem::StyledByItem relation could be
// characterized as weak.
// std::set<IfcSchema::Type::Enum> weak_roots;
if (entity != file_entity) {
throw ifcopenshell::exception("Instance not part of this file");
}
if (batch_mode_) {
batch_deletion_ids_.push_back(id);
} else {
process_deletion_(entity);
byid_.erase(entity.id());
}
}
void file::process_deletion_(const express::Base& entity) {
auto references = instances_by_reference(entity.id());
// Alter entity instances with INVERSE relations to the entity being
// deleted. This is necessary to maintain a valid IFC file, because
// dangling references to it's entities name should be removed. At this
// moment, inversely related instances affected by the removal of the
// entity being deleted are not deleted themselves.
if (!references.empty()) {
for (auto& related_instance : references) {
if (std::find(batch_deletion_ids_.begin(), batch_deletion_ids_.end(), related_instance.id()) != batch_deletion_ids_.end()) {
continue;
}
const auto& decl = related_instance.declaration();
for (size_t i = 0; i < (decl.as_entity() ? decl.as_entity()->attribute_count() : 1); ++i) {
auto attr = related_instance.get_attribute_value(i);
if (attr.isNull()) {
continue;
}
ifcopenshell::argument_type attr_type = attr.type();
switch (attr_type) {
case ifcopenshell::Argument_ENTITY_INSTANCE: {
express::Base instance_attribute = attr;
if (instance_attribute == entity) {
related_instance.set_attribute_value(i, blank{});
}
} break;
case ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE: {
std::vector<express::Base> instance_list = attr;
auto it = std::remove(instance_list.begin(), instance_list.end(), entity);
if (it != instance_list.end()) {
instance_list.erase(it, instance_list.end());
if (instance_list.empty() && related_instance.declaration().as_entity()->attribute_by_index(i)->optional()) {
// @todo we can also check the lower bound of the attribute type before setting to null.
related_instance.set_attribute_value(i, blank{});
} else {
related_instance.set_attribute_value(i, instance_list);
}
}
} break;
case ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE: {
std::vector<std::vector<express::Base>> instance_list_list = attr;
bool updated = false;
for (auto& li : instance_list_list) {
auto it = std::remove(li.begin(), li.end(), entity);
if (it != li.end()) {
li.erase(it, li.end());
updated = true;
}
}
related_instance.set_attribute_value(i, instance_list_list);
} break;
default:
break;
}
}
}
}
if (entity.declaration().is(*ifcroot_type_) && !entity.get_attribute_value(0).isNull()) {
const std::string global_id = entity.get_attribute_value(0);
auto it = byguid_.find(global_id);
if (it != byguid_.end()) {
byguid_.erase(it);
} else {
logger_.get().warning("GlobalId on rooted instance not encountered in map");
}
}
process_deletion_inverse(entity);
remove_type_ref(entity);
// entity_file_map is in place to prevent duplicate definitions with usage of add().
// Upon deletion the pairs need to be erased.
// @todo this is not strictly necessary anymore and can be amortized to e.g 1/100 times
// to delete the expired weak_ptrs.
for (auto it = entity_file_map_.begin(); it != entity_file_map_.end();) {
if (it->second == entity) {
it = entity_file_map_.erase(it);
} else {
++it;
}
}
}
void ifcopenshell::impl::in_memory_file_storage::process_deletion_inverse(const express::Base& entity) {
auto id = entity.id();
// Delete inverses into entity
byref_excl_.erase(id);
byref_excl_.remove_source(id);
}
namespace {
template <typename Fn>
void visit_subtypes(const ifcopenshell::entity* ent, Fn fn) {
fn(ent);
for (const auto& st : ent->subtypes()) {
visit_subtypes(st, fn);
}
}
template <typename Fn>
void visit_supertypes(const ifcopenshell::entity* ent, Fn fn) {
fn(ent);
if (ent->supertype()) {
visit_supertypes(ent->supertype(), fn);
}
}
}
std::vector<express::Base> file::instances_by_type(const ifcopenshell::declaration* t) {
std::vector<express::Base> insts;
if (t->as_entity() != nullptr) {
visit_subtypes(t->as_entity(), [this, &insts](const ifcopenshell::entity* ent) {
auto subtype_insts = instances_by_type_excl_subtypes(ent);
insts.insert(insts.end(), subtype_insts.begin(), subtype_insts.end());
});
}
return insts;
}
std::vector<express::Base> file::instances_by_type_excl_subtypes(const ifcopenshell::declaration* t) {
return std::visit([t](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
auto it = x.bytype_excl_.find(t);
return (it == x.bytype_excl_.end()) ? std::vector<express::Base>{} : it->second;
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
std::vector<express::Base> ret;
auto it = x.bytype_.find(t->index_in_schema());
if (it != x.bytype_.end()) {
const auto& s = it->second;
// @todo generalize this, bytype_ should be a map_adapter
std::vector<size_t> vals(s.size() / sizeof(size_t));
memcpy(vals.data(), s.data(), s.size());
for (auto& v : vals) {
ret.push_back(x.assert_existance(v, ifcopenshell::impl::rocks_db_file_storage::entityinstance_ref));
}
}
return ret;
} else {
throw std::runtime_error("Storage not initialized");
std::vector<express::Base> ret;
return ret;
}
}, storage_);
}
std::vector<express::Base> file::instances_by_type(const std::string& t) {
return instances_by_type(schema()->declaration_by_name(t));
}
std::vector<express::Base> file::instances_by_type_excl_subtypes(const std::string& t) {
return instances_by_type_excl_subtypes(schema()->declaration_by_name(t));
}
std::vector<express::Base> file::instances_by_reference(int t) {
std::vector<express::Base> ret;
std::visit([this, t, &ret](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
auto range = x.byref_excl_.equal_range((uint32_t)t);
ret.reserve(ret.size() + (size_t)std::distance(range.first, range.second));
for (auto it = range.first; it != range.second; ++it) {
ret.push_back(instance_by_id(it->source_id));
}
}
#ifdef IFOPSH_WITH_ROCKSDB
else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
// @todo no lower/upper_bounds() implemented yet
auto prefix = "v|" + std::to_string(t) + "|";
auto it = std::unique_ptr<rocksdb::Iterator>(x.db->NewIterator(rocksdb::ReadOptions()));
it->Seek(prefix);
while (it->Valid() && it->key().starts_with(prefix)) {
std::vector<uint32_t> vals(it->value().size() / sizeof(uint32_t));
memcpy(vals.data(), it->value().data(), it->value().size());
for (auto& v : vals) {
ret.push_back(instance_by_id(v));
}
it->Next();
}
}
#endif
else {
throw std::runtime_error("Storage not initialized");
}
}, storage_);
return ret;
}
express::Base file::instance_by_id(int id) {
return std::visit([id](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
throw std::runtime_error("Storage not initialized");
return express::Base{};
} else {
return x.instance_by_id(id);
}
}, storage_);
}
void ifcopenshell::file::add_type_ref(const express::Base& new_entity)
{
std::visit([new_entity](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
throw std::runtime_error("Storage not initialized");
} else {
return x.add_type_ref(new_entity);
}
}, storage_);
}
void ifcopenshell::file::remove_type_ref(const express::Base& new_entity) {
std::visit([new_entity](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
throw std::runtime_error("Storage not initialized");
} else {
return x.remove_type_ref(new_entity);
}
}, storage_);
}
void ifcopenshell::file::process_deletion_inverse(const express::Base& inst) {
std::visit([inst](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
throw std::runtime_error("Storage not initialized");
} else {
return x.process_deletion_inverse(inst);
}
}, storage_);
}
express::Base file::instance_by_guid(const std::string& guid) {
auto it = byguid_.find(guid);
if (it == byguid_.end()) {
throw exception("Instance with GlobalId '" + guid + "' not found");
}
return it->second;
}
file::type_iterator file::types_begin() const {
return std::visit([](const auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
return file::type_iterator{ impl::rocks_db_file_storage::rocksdb_types_iterator{} };
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
return file::type_iterator{ x.bytype_excl_.begin() };
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
return file::type_iterator{ impl::rocks_db_file_storage::rocksdb_types_iterator(&x) };
}
}, storage_);
}
file::type_iterator file::types_end() const {
return std::visit([](const auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
return file::type_iterator{ impl::rocks_db_file_storage::rocksdb_types_iterator{} };
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
return file::type_iterator{ x.bytype_excl_.end() };
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
return file::type_iterator{ impl::rocks_db_file_storage::rocksdb_types_iterator{} };
}
}, storage_);
}
std::ostream& operator<<(std::ostream& out, const ifcopenshell::file& file) {
file.header().write(out);
typedef std::vector<express::Base> vector_t;
vector_t sorted;
std::transform(file.begin(), file.end(), std::back_inserter(sorted), [&file](const auto& x) { return x.second; });
std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) { return a.id() < b.id(); });
for (auto& e : sorted) {
// @todo this check should no longer be necessary?
if (e.declaration().as_entity() != nullptr) {
e.to_string(out, true);
out << ";" << std::endl;
}
}
out << "ENDSEC;" << std::endl;
out << "END-ISO-10303-21;" << std::endl;
return out;
}
std::string file::create_timestamp() {
char buf[255];
time_t t;
time(&t);
struct tm* ti = localtime(&t);
std::string result;
if (strftime(buf, 255, "%Y-%m-%dT%H:%M:%S", ti) != 0U) {
result = std::string(buf);
}
return result;
}
const ifcopenshell::schema_definition* file::schema() const {
if (schema_ == nullptr) {
throw exception("No schema loaded");
}
return schema_;
}
std::vector<int> file::get_inverse_indices_by_id(int instance_id) {
std::vector<int> return_value;
// Mapping of instance id to attribute offset.
std::map<int, std::vector<int>> mapping;
bool handled = false;
std::visit([&mapping, &return_value, &handled, instance_id](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
handled = true;
auto range = x.byref_excl_.equal_range((uint32_t)instance_id);
return_value.reserve((size_t)std::distance(range.first, range.second));
for (auto it = range.first; it != range.second; ++it) {
return_value.push_back(it->attribute_index);
}
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
#ifdef IFOPSH_WITH_ROCKSDB
// @todo no lower/upper_bounds() implemented yet
auto prefix = "v|" + std::to_string(instance_id) + "|";
auto it = std::unique_ptr<rocksdb::Iterator>(x.db->NewIterator(rocksdb::ReadOptions()));
it->Seek(prefix);
while (it->Valid() && it->key().starts_with(prefix)) {
std::vector<uint32_t> vals(it->value().size() / sizeof(uint32_t));
memcpy(vals.data(), it->value().data(), it->value().size());
auto tuple = key_from_string<std::tuple<int, int, int>>(it->key().ToString().substr(2));
for (auto& i : vals) {
mapping[i].push_back(std::get<2>(tuple));
}
it->Next();
}
#endif
}
}, storage_);
if (handled) {
return return_value;
}
auto refs = instances_by_reference(instance_id);
for (const auto& ref : refs) {
auto it = mapping.find(ref.id());
if (it == mapping.end() || it->second.empty()) {
throw exception("Internal error");
}
return_value.push_back(it->second.front());
it->second.erase(it->second.begin());
if (it->second.empty()) {
mapping.erase(it);
}
}
// Test whether all mappings where indeed used.
if (!mapping.empty()) {
throw exception("Internal error");
}
return return_value;
}
std::vector<express::Entity> file::get_inverse(int instance_id, const ifcopenshell::declaration* type, int attribute_index) {
std::vector<express::Entity> return_value;
if (type == nullptr && attribute_index == -1) {
// @todo this is silly.
auto r = instances_by_reference(instance_id);
for (auto& i : r) {
return_value.push_back(i.as<express::Entity>());
}
return return_value;
}
std::visit([&return_value, this, attribute_index, instance_id, type](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
std::vector<uint8_t> source_types(schema()->declarations().size(), 0);
visit_subtypes(type->as_entity(), [&source_types](const ifcopenshell::declaration* ent) {
source_types[ent->index_in_schema()] = 1;
});
auto range = x.byref_excl_.equal_range((uint32_t)instance_id);
for (auto it = range.first; it != range.second; ++it) {
if (it->source_entity < source_types.size() && source_types[it->source_entity] &&
(attribute_index == -1 || it->attribute_index == attribute_index)) {
return_value.push_back(instance_by_id(it->source_id).template as<express::Entity>());
}
}
}
#ifdef IFOPSH_WITH_ROCKSDB
else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
visit_subtypes(type->as_entity(), [this, attribute_index, instance_id, &return_value, &x](const ifcopenshell::declaration* ent) {
if (attribute_index == -1) {
// @todo no lower/upper_bounds() implemented yet
auto prefix = "v|" + std::to_string(instance_id) + "|" + std::to_string(ent->index_in_schema()) + "|";
auto it = std::unique_ptr<rocksdb::Iterator>(x.db->NewIterator(rocksdb::ReadOptions()));
it->Seek(prefix);
while (it->Valid() && it->key().starts_with(prefix)) {
std::vector<uint32_t> vals(it->value().size() / sizeof(uint32_t));
memcpy(vals.data(), it->value().data(), it->value().size());
for (auto& v : vals) {
return_value.push_back(instance_by_id(v).template as<express::Entity>());
}
it->Next();
}
} else {
auto it = x.byref_excl_.find({instance_id, ent->index_in_schema(), attribute_index});
if (it != x.byref_excl_.end()) {
for (auto& i : it->second) {
return_value.push_back(instance_by_id(i).template as<express::Entity>());
}
}
}
});
}
#endif
}, storage_);
return return_value;
}
size_t file::get_total_inverses(int instance_id) {
std::set<uint32_t> counted_ids;
std::visit([&counted_ids, instance_id](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
auto range = x.byref_excl_.equal_range((uint32_t)instance_id);
for (auto it = range.first; it != range.second; ++it) {
counted_ids.insert(it->source_id);
}
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
// @todo
}
}, storage_);
return counted_ids.size();
}
void file::set_default_header_values() {
const std::string empty_string;
std::vector<std::string> file_description;
std::vector<std::string> schema_identifiers;
std::vector<std::string> string_vector = {""};
file_description.push_back("ViewDefinition [CoordinationView]");
if (schema() != nullptr) {
schema_identifiers.push_back(schema()->name());
}
header().file_description().setdescription(file_description);
header().file_description().setimplementation_level("2;1");
header().file_name().setname(empty_string);
header().file_name().settime_stamp(create_timestamp());
header().file_name().setauthor(string_vector);
header().file_name().setorganization(string_vector);
header().file_name().setpreprocessor_version("IfcOpenShell " + std::string(IFCOPENSHELL_VERSION));
header().file_name().setoriginating_system("IfcOpenShell " + std::string(IFCOPENSHELL_VERSION));
header().file_name().setauthorization(empty_string);
header().file_schema().setschema_identifiers(schema_identifiers);
}
std::pair<express::Base, double> file::get_unit(const std::string& unit_type) {
std::pair<express::Base, double> return_value(express::Base{}, 1.);
auto projects = instances_by_type(schema()->declaration_by_name("IfcProject"));
if (projects.empty()) {
try {
projects = instances_by_type(schema()->declaration_by_name("IfcContext"));
} catch (exception&) {
}
}
if (!projects.empty()) {
auto project = *projects.begin();
express::Base unit_assignment = project.as<express::Entity>().get("UnitsInContext");
std::vector<express::Base> units = unit_assignment.as<express::Entity>().get("Units");
for (auto& unit : units) {
if (unit.declaration().is("IfcNamedUnit")) {
const std::string file_unit_type = unit.as<express::Entity>().get("UnitType");
if (file_unit_type != unit_type) {
continue;
}
express::Base siunit;
if (unit.declaration().is("IfcConversionBasedUnit")) {
express::Base mu = unit.as<express::Entity>().get("ConversionFactor");
express::Base vlc = mu.as<express::Entity>().get("ValueComponent");
express::Base unc = mu.as<express::Entity>().get("UnitComponent");
return_value.second *= static_cast<double>(vlc.get_attribute_value(0));
return_value.first = unit;
if (unc.declaration().is("IfcSIUnit")) {
siunit = unc;
}
} else if (unit.declaration().is("IfcSIUnit")) {
return_value.first = siunit = unit;
}
if (siunit) {
attribute_value prefix = siunit.as<express::Entity>().get("Prefix");
if (!prefix.isNull()) {
return_value.second *= si_prefix_to_value(prefix);
}
}
}
}
}
return return_value;
}
void ifcopenshell::file::build_inverses_(const express::Base& inst) {
std::function<void(const express::Base&, int)> fn = [this, inst](const express::Base& attr, int idx) {
if (attr.declaration().as_entity() != nullptr) {
unsigned entity_attribute_id = attr.id();
const auto* decl = inst.declaration().as_entity();
std::visit([entity_attribute_id, decl, idx, inst](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
x.byref_excl_.add(entity_attribute_id, inst.id(), (uint16_t)decl->index_in_schema(), idx);
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
// @todo
}
}, storage_);
}
};
apply_individual_instance_visitor(inst).apply(fn);
}
void ifcopenshell::file::unbatch() {
for (auto& id : batch_deletion_ids_) {
process_deletion_(instance_by_id(id));
}
// keep in memory until all deletions are processed
for (auto& id : batch_deletion_ids_) {
byid_.erase(id);
}
batch_mode_ = false;
batch_deletion_ids_.clear();
}
void ifcopenshell::file::reset_identity_cache() {
std::visit([](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
std::lock_guard<std::mutex> lock(x.instance_cache_mutex_);
x.instance_cache_.clear();
x.type_instance_cache_.clear();
}
}, storage_);
}
void ifcopenshell::file::build_inverses() {
for (const auto& pair : *this) {
build_inverses_(express::Base(pair.second));
}
}
void ifcopenshell::file::register_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, int inst_id, int attribute_index)
{
std::visit([id_from, from_entity, inst_id, attribute_index](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
throw std::runtime_error("Storage not initialized");
} else {
return x.register_inverse(id_from, from_entity, inst_id, attribute_index);
}
}, storage_);
}
void ifcopenshell::file::unregister_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, const express::Base& inst, int attribute_index)
{
std::visit([id_from, from_entity, inst, attribute_index](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
throw std::runtime_error("Storage not initialized");
} else {
return x.unregister_inverse(id_from, from_entity, inst, attribute_index);
}
}, storage_);
}
std::atomic_uint32_t instance_data::counter_(0);
// bool ifcopenshell::file::guid_map_ = true;
void express::Base::unset_attribute_value(size_t index) {
data()->set_attribute_value(index, blank{});
}
attribute_value express::Base::get_attribute_value(size_t index) const {
return data()->get_attribute_value(index);
}
void express::Base::to_string(std::ostream& out, bool upper) const
{
const auto *ent = declaration().as_entity();
if (ent != nullptr && declaration().schema() != &Header_section_schema::get_schema()) {
out << "#" << id() << "=";
}
if (upper) {
out << declaration().name_uc();
} else {
out << declaration().name();
}
data()->to_string(out, upper);
}
ifcopenshell::file* express::Base::file() const {
return data()->file();
}
/*
instance_data::instance_data(const instance_data& data)
: storage_(data.size())
{
}
*/
attribute_value instance_data::get_attribute_value(size_t index) const
{
if (storage_) {
return attribute_value(storage_, (uint8_t)index);
} else {
auto* const storage = std::visit([](auto& m) -> ifcopenshell::impl::rocks_db_file_storage* {
using U = std::decay_t<decltype(m)>;
if constexpr (std::is_same_v<U, ifcopenshell::impl::rocks_db_file_storage>) {
return &m;
} else {
return nullptr;
}
}, file_->storage_);
return attribute_value(storage, id_ ? id_ : identity_, declaration_, (uint8_t)index);
}
}
bool ifcopenshell::impl::rocks_db_file_storage::read_schema(const ifcopenshell::schema_definition*& schema) {
#ifdef IFOPSH_WITH_ROCKSDB
std::string value;
auto key = "h|file_schema|0";
db->Get(rocksdb::ReadOptions{}, key, &value);
std::vector<std::string> strings;
if (::impl::deserialize(this, value, strings) && strings.size() == 1) {
try {
schema = schema_by_name(strings[0]);
} catch (exception&) {
return false;
}
return true;
}
#endif
return false;
}
/*
express::Base::IfcBaseClass(instance_data&& data)
: identity_(counter_++)
, id_(0)
, file_(nullptr)
, data_(std::move(data))
{
* @todo this is not allowed cannot call virtual func in constructor
if (!declaration().as_entity()) {
// @nb from v0.9 type decl instances have their own id, which may collide with instance names in the file
// but is otherwise unique
id_ = identity_;
}
}
*/
void express::Base::set_attribute_value(size_t i, const express::Base& p) {
set_attribute_value<express::Base>(i, p);
}
void express::Base::set_attribute_value(const std::string& name, const express::Base& p) {
set_attribute_value<express::Base>(name, p);
}
template void IFC_PARSE_API express::Base::set_attribute_value<blank>(size_t index, const blank& value);
template void IFC_PARSE_API express::Base::set_attribute_value<derived>(size_t index, const derived& value);
template void IFC_PARSE_API express::Base::set_attribute_value<int64_t>(size_t index, const int64_t& value);
template void IFC_PARSE_API express::Base::set_attribute_value<bool>(size_t index, const bool& value);
template void IFC_PARSE_API express::Base::set_attribute_value<boost::logic::tribool>(size_t index, const boost::logic::tribool& value);
template void IFC_PARSE_API express::Base::set_attribute_value<double>(size_t index, const double& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::string>(size_t index, const std::string& value);
template void IFC_PARSE_API express::Base::set_attribute_value<boost::dynamic_bitset<>>(size_t index, const boost::dynamic_bitset<>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<enumeration_reference>(size_t index, const enumeration_reference& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<int64_t>>(size_t index, const std::vector<int64_t>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<double>>(size_t index, const std::vector<double>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::string>>(size_t index, const std::vector<std::string>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<boost::dynamic_bitset<>>>(size_t index, const std::vector<boost::dynamic_bitset<>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<express::Base>>(size_t index, const std::vector<express::Base>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::vector<int64_t>>>(size_t index, const std::vector<std::vector<int64_t>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::vector<double>>>(size_t index, const std::vector<std::vector<double>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::vector<express::Base>>>(size_t index, const std::vector<std::vector<express::Base>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<blank>(const std::string& name, const blank& value);
template void IFC_PARSE_API express::Base::set_attribute_value<derived>(const std::string& name, const derived& value);
template void IFC_PARSE_API express::Base::set_attribute_value<int64_t>(const std::string& name, const int64_t& value);
template void IFC_PARSE_API express::Base::set_attribute_value<bool>(const std::string& name, const bool& value);
template void IFC_PARSE_API express::Base::set_attribute_value<boost::logic::tribool>(const std::string& name, const boost::logic::tribool& value);
template void IFC_PARSE_API express::Base::set_attribute_value<double>(const std::string& name, const double& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::string>(const std::string& name, const std::string& value);
template void IFC_PARSE_API express::Base::set_attribute_value<boost::dynamic_bitset<>>(const std::string& name, const boost::dynamic_bitset<>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<enumeration_reference>(const std::string& name, const enumeration_reference& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<int64_t>>(const std::string& name, const std::vector<int64_t>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<double>>(const std::string& name, const std::vector<double>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::string>>(const std::string& name, const std::vector<std::string>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<boost::dynamic_bitset<>>>(const std::string& name, const std::vector<boost::dynamic_bitset<>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<express::Base>>(const std::string& name, const std::vector<express::Base>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::vector<int64_t>>>(const std::string& name, const std::vector<std::vector<int64_t>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::vector<double>>>(const std::string& name, const std::vector<std::vector<double>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::vector<express::Base>>>(const std::string& name, const std::vector<std::vector<express::Base>>& value);
namespace express {
template <typename T>
T Entity::get_value(const std::string& name) const {
auto attr = get(name);
T v = attr;
return v;
}
template <typename T>
T Entity::get_value(const std::string& name, const T& default_value) const {
auto attr = get(name);
if (attr.isNull()) {
return default_value;
}
T v = attr;
return v;
}
} // namespace express
template int64_t IFC_PARSE_API express::Entity::get_value<int64_t>(const std::string&) const;
template bool IFC_PARSE_API express::Entity::get_value<bool>(const std::string&) const;
template boost::logic::tribool IFC_PARSE_API express::Entity::get_value<boost::logic::tribool>(const std::string&) const;
template double IFC_PARSE_API express::Entity::get_value<double>(const std::string&) const;
template std::string IFC_PARSE_API express::Entity::get_value<std::string>(const std::string&) const;
template express::Base IFC_PARSE_API express::Entity::get_value<express::Base>(const std::string&) const;
template boost::dynamic_bitset<> IFC_PARSE_API express::Entity::get_value<boost::dynamic_bitset<>>(const std::string&) const;
template enumeration_reference IFC_PARSE_API express::Entity::get_value<enumeration_reference>(const std::string&) const;
template std::vector<int64_t> IFC_PARSE_API express::Entity::get_value<std::vector<int64_t>>(const std::string&) const;
template std::vector<double> IFC_PARSE_API express::Entity::get_value<std::vector<double>>(const std::string&) const;
template std::vector<std::string> IFC_PARSE_API express::Entity::get_value<std::vector<std::string>>(const std::string&) const;
template std::vector<boost::dynamic_bitset<>> IFC_PARSE_API express::Entity::get_value<std::vector<boost::dynamic_bitset<>>>(const std::string&) const;
template std::vector<express::Base> IFC_PARSE_API express::Entity::get_value<std::vector<express::Base>>(const std::string&) const;
template std::vector<std::vector<int64_t>> IFC_PARSE_API express::Entity::get_value<std::vector<std::vector<int64_t>>>(const std::string&) const;
template std::vector<std::vector<double>> IFC_PARSE_API express::Entity::get_value<std::vector<std::vector<double>>>(const std::string&) const;
template std::vector<std::vector<express::Base>> IFC_PARSE_API express::Entity::get_value<std::vector<std::vector<express::Base>>>(const std::string&) const;
template int64_t IFC_PARSE_API express::Entity::get_value<int64_t>(const std::string&, const int64_t&) const;
template bool IFC_PARSE_API express::Entity::get_value<bool>(const std::string&, const bool&) const;
template boost::logic::tribool IFC_PARSE_API express::Entity::get_value<boost::logic::tribool>(const std::string&, const boost::logic::tribool&) const;
template double IFC_PARSE_API express::Entity::get_value<double>(const std::string&, const double&) const;
template std::string IFC_PARSE_API express::Entity::get_value<std::string>(const std::string&, const std::string&) const;
template express::Base IFC_PARSE_API express::Entity::get_value<express::Base>(const std::string&, const express::Base&) const;
template boost::dynamic_bitset<> IFC_PARSE_API express::Entity::get_value<boost::dynamic_bitset<>>(const std::string&, const boost::dynamic_bitset<>&) const;
template enumeration_reference IFC_PARSE_API express::Entity::get_value<enumeration_reference>(const std::string&, const enumeration_reference&) const;
template std::vector<int64_t> IFC_PARSE_API express::Entity::get_value<std::vector<int64_t>>(const std::string&, const std::vector<int64_t>&) const;
template std::vector<double> IFC_PARSE_API express::Entity::get_value<std::vector<double>>(const std::string&, const std::vector<double>&) const;
template std::vector<std::string> IFC_PARSE_API express::Entity::get_value<std::vector<std::string>>(const std::string&, const std::vector<std::string>&) const;
template std::vector<boost::dynamic_bitset<>> IFC_PARSE_API express::Entity::get_value<std::vector<boost::dynamic_bitset<>>>(const std::string&, const std::vector<boost::dynamic_bitset<>>&) const;
template std::vector<express::Base> IFC_PARSE_API express::Entity::get_value<std::vector<express::Base>>(const std::string&, const std::vector<express::Base>&) const;
template std::vector<std::vector<int64_t>> IFC_PARSE_API express::Entity::get_value<std::vector<std::vector<int64_t>>>(const std::string&, const std::vector<std::vector<int64_t>>&) const;
template std::vector<std::vector<double>> IFC_PARSE_API express::Entity::get_value<std::vector<std::vector<double>>>(const std::string&, const std::vector<std::vector<double>>&) const;
template std::vector<std::vector<express::Base>> IFC_PARSE_API express::Entity::get_value<std::vector<std::vector<express::Base>>>(const std::string&, const std::vector<std::vector<express::Base>>&) const;