/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see . *
* *
********************************************************************************/
#include "parse.h"
#include "express.h"
#include "character_decoder.h"
#include "exception.h"
#include "file.h"
#include "logger.h"
#include "schema.h"
#include "si_prefix.h"
#include "file_reader.h"
#include "utils.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef USE_MMAP
#include
#endif
#define PERMISSIVE_FLOAT
using namespace ifcopenshell;
// A static locale for the real number parser. strtod() is locale-dependent, causing issues
// in locales that have ',' as a decimal separator. Therefore the non standard _strtod_l() /
// strtod_l() is used and a reference to the "C" locale is obtained here. The alternative is
// to use std::istringstream::imbue(std::locale::classic()), but there are subtleties in
// parsing in MSVC2010 and it appears to be much slower.
#if defined(_MSC_VER)
static _locale_t locale = (_locale_t)0;
void init_locale() {
if (locale == (_locale_t)0) {
locale = _create_locale(LC_NUMERIC, "C");
}
}
#else
#if defined(__MINGW64__) || defined(__MINGW32__)
#include
#include
typedef void* locale_t;
static locale_t locale = (locale_t)0;
void init_locale() {}
double strtod_l(const char* start, char** end, locale_t loc) {
double d;
std::stringstream ss;
ss.imbue(std::locale::classic());
ss << start;
ss >> d;
size_t nread = ss.tellg();
*end = const_cast(start) + nread;
return d;
}
#else
#ifdef __APPLE__
#include
#endif
#include
static locale_t locale = (locale_t)0;
void init_locale() {
if (locale == (locale_t)0) {
locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
}
}
#endif
#endif
template
spf_lexer::spf_lexer(Reader* stream_) {
stream = stream_;
decoder_ = new character_decoder(stream_);
}
template
spf_lexer::~spf_lexer() {
delete decoder_;
}
template
size_t spf_lexer::skip_whitespace() const {
size_t index = 0;
while (!stream->eof()) {
char character = stream->peek();
if ((character == ' ' || character == '\r' || character == '\n' || character == '\t')) {
stream->increment();
++index;
} else {
break;
}
}
return index;
}
template
size_t spf_lexer::skip_comment() const {
if (stream->eof()) {
return 0;
}
char character = stream->peek();
if (character != '/') {
return 0;
}
stream->increment();
character = stream->peek();
if (character != '*') {
stream->seek(stream->tell() - 1);
return 0;
}
size_t index = 2;
char intermediate = 0;
while (!stream->eof()) {
character = stream->peek();
stream->increment();
++index;
if (character == '/' && intermediate == '*') {
break;
}
intermediate = character;
}
return index;
}
template
std::string& spf_lexer::get_temp_string() const {
const size_t idx = pool_index++;
const size_t slice = idx >> 4;
const size_t offset = idx & 0xF;
while (stringpool_.size() <= slice) {
stringpool_.push_back(std::make_unique>());
}
// std::wcout << "Num contexts: " << idx << std::endl;
return (*stringpool_[slice])[offset];
}
namespace {
bool parse_int_(const char* pStart, int& val) {
char* pEnd;
long result = strtol(pStart, &pEnd, 10);
if (*pEnd != 0) {
return false;
}
val = (int)result;
return true;
}
bool parse_float_(const char* pStart, double& val) {
char* pEnd;
#ifdef _MSC_VER
double result = _strtod_l(pStart, &pEnd, locale);
#else
double result = strtod_l(pStart, &pEnd, locale);
#endif
if (*pEnd != 0) {
return false;
}
val = result;
return true;
}
} // namespace
namespace SWAR {
constexpr uint32_t ONES32 = 0x01010101u;
constexpr uint32_t HIGHS32 = 0x80808080u;
constexpr uint64_t ONES = 0x0101010101010101ull;
constexpr uint64_t HIGHS = 0x8080808080808080ull;
constexpr uint64_t splat(unsigned char c) {
return ONES * c;
}
inline uint32_t has_zero_byte(uint32_t x) {
return (x - ONES32) & ~x & HIGHS32;
}
inline uint64_t has_zero_byte(uint64_t x) {
return (x - ONES) & ~x & HIGHS;
}
inline uint32_t eq_mask(uint32_t x, uint32_t c) {
return has_zero_byte(x ^ c);
}
inline uint64_t eq_mask(uint64_t x, uint64_t c) {
return has_zero_byte(x ^ c);
}
namespace chars {
constexpr uint64_t lpar = splat('(');
constexpr uint64_t rpar = splat(')');
constexpr uint64_t eq = splat('=');
constexpr uint64_t comma = splat(',');
constexpr uint64_t semi = splat(';');
constexpr uint64_t slash = splat('/');
constexpr uint64_t space = splat(' ');
constexpr uint64_t cr = splat('\r');
constexpr uint64_t lf = splat('\n');
constexpr uint64_t tab = splat('\t');
constexpr uint64_t quote = splat('"');
constexpr uint64_t dot = splat('.');
} // namespace chars
template
inline uint64_t has_special_char(uint64_t x) {
return eq_mask(x, chars::lpar) |
eq_mask(x, chars::rpar) |
eq_mask(x, chars::eq) |
eq_mask(x, chars::comma) |
eq_mask(x, chars::semi) |
eq_mask(x, chars::slash) |
eq_mask(x, chars::space) |
eq_mask(x, chars::cr) |
eq_mask(x, chars::lf) |
eq_mask(x, chars::tab) |
eq_mask(x, chars::quote) |
(IncludeDot ? eq_mask(x, chars::dot) : uint64_t{0});
}
template
inline uint32_t has_special_char(uint32_t x) {
return eq_mask(x, static_cast(chars::lpar)) |
eq_mask(x, static_cast(chars::rpar)) |
eq_mask(x, static_cast(chars::eq)) |
eq_mask(x, static_cast(chars::comma)) |
eq_mask(x, static_cast(chars::semi)) |
eq_mask(x, static_cast(chars::slash)) |
eq_mask(x, static_cast(chars::space)) |
eq_mask(x, static_cast(chars::cr)) |
eq_mask(x, static_cast(chars::lf)) |
eq_mask(x, static_cast(chars::tab)) |
eq_mask(x, static_cast(chars::quote)) |
(IncludeDot ? eq_mask(x, static_cast(chars::dot)) : uint32_t{0});
}
}
//
// Returns the offset of the current token and moves cursor to next
//
template
token spf_lexer::next() {
if (stream->eof()) {
return token{};
}
auto pos = stream->tell();
char character = stream->read();
if (character == '/' || character == ' ' || character == '\r' || character == '\n' || character == '\t') {
while ((skip_whitespace() != 0U) || (skip_comment() != 0U)) {
}
if (stream->eof()) {
return token{};
}
pos = stream->tell();
character = stream->read();
}
// If the cursor is at [()=,;$*] we know token consists of single char
if (character == '(' ||
character == ')' ||
character == '=' ||
character == ',' ||
character == ';' ||
character == '$' ||
character == '*')
{
return token(pos, character);
}
auto& str = get_temp_string();
if (character == '\'') {
// If a string is encountered defer processing to the character_decoder
str = *decoder_;
return token(pos, token::Token_STRING, str);
} else {
auto ttype = token::Token_NONE;
if (character == '"' || character == '.') {
if (character == '"') {
ttype = token::Token_BINARY;
} else {
ttype = token::Token_ENUMERATION;
}
str.clear();
} else if (character == '#') {
ttype = token::Token_IDENTIFIER;
str.clear();
} else {
str.assign(&character, 1);
}
auto remaining = stream->remaining();
while (remaining) {
if (remaining >= 8) {
uint64_t x = stream->peek_u64();
if ((ttype == token::Token_NONE ? SWAR::has_special_char(x) : SWAR::has_special_char(x)) == 0) {
str.append(reinterpret_cast(&x), 8);
stream->increment(8);
remaining -= 8;
continue;
}
}
if (remaining >= 4) {
uint32_t x = stream->peek_u32();
if ((ttype == token::Token_NONE ? SWAR::has_special_char(x) : SWAR::has_special_char(x)) == 0) {
str.append(reinterpret_cast(&x), 4);
stream->increment(4);
remaining -= 4;
continue;
}
}
// Read character and increment pointer if not starting a new token
char character = stream->peek();
if (character == '(' ||
character == ')' ||
character == '=' ||
character == ',' ||
character == ';' ||
character == '/') {
break;
}
if (!(character == ' ' || character == '\r' || character == '\n' || character == '\t')) {
if ((ttype == token::Token_BINARY && character == '"') ||
(ttype == token::Token_ENUMERATION && character == '.')) {
// Skip
} else {
str.push_back(character);
}
}
stream->increment();
remaining -= 1;
}
if (ttype == token::Token_ENUMERATION && str.size() == 1 && (str[0] == 'T' || str[0] == 'F' || str[0] == 'U')) {
pop_pool_entry();
return token(pos, token::Token_BOOL, str[0]);
} else if (ttype == token::Token_IDENTIFIER) {
int int_val;
if (!parse_int_(str.c_str(), int_val)) {
throw invalid_token_exception(pos, str, "instance name");
}
pop_pool_entry();
return token(pos, ttype, int_val);
} else if (ttype == token::Token_NONE && !str.empty()) {
int 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_int_(str.c_str(), int_val)) {
ttype = token::Token_INT;
pop_pool_entry();
return token(pos, ttype, int_val);
} else if (parse_float_(str.c_str(), 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 spf_lexer>;
template class spf_lexer>;
template class spf_lexer>;
#ifdef USE_MMAP
template class spf_lexer>;
#endif
bool token::is_operator() {
return type == Token_OPERATOR;
}
bool token::is_operator(char character) {
return type == Token_OPERATOR && value_char == character;
}
bool token::is_identifier() {
return type == Token_IDENTIFIER;
}
bool token::is_string() {
return type == Token_STRING;
}
bool token::is_enumeration() {
// @nb this is a bit confusing?
return type == Token_ENUMERATION || type == Token_BOOL;
}
bool token::is_binary() {
return type == Token_BINARY;
}
bool token::is_keyword() {
return type == Token_KEYWORD;
}
bool token::is_int() {
return type == Token_INT;
}
bool token::is_bool() {
// Bool and logical share the same storage type, just logical unknown is stored as 'U'.
return type == Token_BOOL && value_char != 'U';
}
bool token::is_logical() {
return type == Token_BOOL;
}
bool token::is_float() {
#ifdef PERMISSIVE_FLOAT
/// NB: We are being more permissive here then allowed by the standard
return type == Token_FLOAT || type == Token_INT;
#else
return type == Token_FLOAT;
#endif
}
int 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;
}
//
// Reads the arguments from a list of token
// Aditionally, registers the ids (i.e. #[\d]+) in the inverse map
//
template
void ifcopenshell::impl::in_memory_file_storage::load(ifcopenshell::spf_lexer* tokens, std::optional entity_instance_name, const ifcopenshell::entity* entity, parse_context& context, int attribute_index) {
token next = tokens->next();
size_t attribute_index_within_data = 0;
size_t return_value = 0;
while (next) {
if (next.is_operator(',')) {
if (attribute_index == -1) {
attribute_index_within_data += 1;
}
} else if (next.is_operator(')')) {
break;
} else if (next.is_operator('(')) {
return_value++;
load(tokens, entity_instance_name, entity, context.push(), attribute_index == -1 ? (int) attribute_index_within_data : attribute_index);
} else {
return_value++;
if (next.is_identifier() && entity && entity_instance_name) {
register_inverse(*entity_instance_name, entity, next.value_int, attribute_index == -1 ? (int) attribute_index_within_data : attribute_index);
}
if (next.is_keyword()) {
try {
const auto* decl = (schema ? schema : file->schema())->declaration_by_name(next.as_string());
parse_context ps(&context_pool_);
tokens->next();
// The only case we know where a defined type contains entity
// instance references is IfcPropertySetDefinitionSet. For
// that purpose we propagate the entity_instance_name to
// register inverses to the host entity (and not the defined
// type) and to be able to actually register the references in
// the 2nd pass.
load(tokens, entity_instance_name, entity, ps, attribute_index == -1 ? (int)attribute_index_within_data : attribute_index);
express::Base simple_type_instance(read_simple_type_instances.emplace_back(
ps.construct(file, entity_instance_name, *references_to_resolve, decl, std::nullopt, attribute_index == -1 ? (int)attribute_index_within_data : attribute_index))
);
// @todo do we need express::Base here? Or should we just push instance_data?
context.push(simple_type_instance);
} catch (exception& e) {
logger::message(logger::LOG_ERROR, std::string(e.what()) + " at offset " + std::to_string(next.start_pos));
// #4070 We didn't actually capture an aggregate entry, undo length increment.
return_value--;
}
} else {
context.push(next);
}
}
next = tokens->next();
}
}
template
void ifcopenshell::impl::in_memory_file_storage::try_read_semicolon(ifcopenshell::spf_lexer* tokens) const {
auto old_offset = tokens->stream->tell();
token semilocon = tokens->next();
if (!semilocon.is_operator(';')) {
tokens->stream->seek(old_offset);
}
}
void ifcopenshell::impl::in_memory_file_storage::register_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, int inst_id, int attribute_index) {
// Assume a check on token type has already been performed
byref_excl_[inst_id][{from_entity->index_in_schema(), attribute_index}].push_back(id_from);
}
void ifcopenshell::impl::in_memory_file_storage::unregister_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, const express::Base& inst, int attribute_index) {
auto& ids = byref_excl_[inst.id()][{from_entity->index_in_schema(), attribute_index}];
auto iter = std::find(ids.begin(), ids.end(), id_from);
if (iter == ids.end()) {
// @todo inverses also need to be populated when multiple instances are added to a new file.
// throw ifcopenshell::exception("Instance not found among inverses");
} else {
ids.erase(iter);
}
}
namespace {
template
std::string to_string_fixed_width(const T& t, size_t) {
// @todo currently inactive
std::ostringstream oss;
oss << /*std::setfill('0') << std::setw(w) <<*/ t;
return oss.str();
}
}
void ifcopenshell::impl::rocks_db_file_storage::register_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, int inst_id, int attribute_index) {
#ifdef IFOPSH_WITH_ROCKSDB
static std::string s;
uint32_t v = id_from;
s.resize(sizeof(uint32_t));
memcpy(s.data(), &v, sizeof(uint32_t));
auto key = "v|" + to_string_fixed_width(inst_id, 10) + "|" + to_string_fixed_width(from_entity->index_in_schema(), 4) + "|" + to_string_fixed_width(attribute_index, 2);
db->Merge(wopts, key, s);
/*
// Python client does not support merges
// @todo turn this into a setting
{
std::string current;
db->Get(rocksdb::ReadOptions{}, key, ¤t);
auto new_val = current + s;
db->Put(wopts, key, new_val);
}*/
#endif
}
void ifcopenshell::impl::rocks_db_file_storage::unregister_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, const express::Base& inst, int attribute_index) {
#ifdef IFOPSH_WITH_ROCKSDB
static std::string s;
auto inst_id = inst.id();
auto key = "v|" + to_string_fixed_width(inst_id, 10) + "|" + to_string_fixed_width(from_entity->index_in_schema(), 4) + "|" + to_string_fixed_width(attribute_index, 2);
if (db->Get(rocksdb::ReadOptions{}, key, &s).ok()) {
std::vector vals(s.size() / sizeof(uint32_t));
memcpy(vals.data(), s.data(), s.size());
auto it = std::find(vals.begin(), vals.end(), (uint32_t)id_from);
if (it != vals.end()) {
vals.erase(it);
} else {
logger::error("Unregistering non-existant inverse #" + std::to_string(id_from) + " on instance #" + std::to_string(inst_id) + " at attribute " + std::to_string(attribute_index));
}
s.resize(vals.size() * sizeof(uint32_t));
memcpy(s.data(), vals.data(), s.size());
db->Put(wopts, key, s);
}
#endif
}
void ifcopenshell::impl::rocks_db_file_storage::add_type_ref(const express::Base& new_entity)
{
#ifdef IFOPSH_WITH_ROCKSDB
size_t v;
std::string s(sizeof(size_t), ' ');
if (new_entity.declaration().as_entity()) {
v = new_entity.id();
memcpy(s.data(), &v, sizeof(size_t));
// no merges yet, because the python client doesn't support them
db->Merge(wopts, "t|" + std::to_string(new_entity.declaration().index_in_schema()), s);
/*{
std::string current;
// @todo this uses the same key-namespace as typedecl instances, not a direct conflict, but also not very clear
auto key = "t|" + std::to_string(new_entity.declaration().index_in_schema());
db->Get(rocksdb::ReadOptions{}, key, ¤t);
auto new_val = current + s;
db->Put(wopts, key, new_val);
}*/
}
// not only mapping also register type
v = new_entity.declaration().index_in_schema();
memcpy(s.data(), &v, sizeof(size_t));
db->Put(wopts, (new_entity.declaration().as_entity() ? "i|" : "t|") + std::to_string(new_entity.id() ? new_entity.id() : new_entity.identity()) + "|_", s);
#endif
}
void ifcopenshell::impl::rocks_db_file_storage::remove_type_ref(const express::Base& new_entity)
{
#ifdef IFOPSH_WITH_ROCKSDB
if (new_entity.declaration().as_entity()) {
std::string s;
auto key = "t|" + std::to_string(new_entity.declaration().index_in_schema());
if (db->Get(rocksdb::ReadOptions{}, key, &s).ok()) {
std::vector vals(s.size() / sizeof(size_t));
memcpy(vals.data(), s.data(), s.size());
vals.erase(std::find(vals.begin(), vals.end(), (size_t)new_entity.id()));
s.resize(vals.size() * sizeof(size_t));
memcpy(s.data(), vals.data(), s.size());
db->Put(wopts, key, s);
}
}
db->Delete(wopts, (new_entity.declaration().as_entity() ? "i|" : "t|") + std::to_string(new_entity.id() ? new_entity.id() : new_entity.identity()) + "|_");
#endif
}
namespace {
class StringBuilderVisitor : public boost::static_visitor {
private:
StringBuilderVisitor(const StringBuilderVisitor&); //N/A
StringBuilderVisitor& operator=(const StringBuilderVisitor&); //N/A
std::ostream& data_;
template
void serialize(const std::vector& i) {
data_ << "(";
for (typename std::vector::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
data_ << *it;
}
data_ << ")";
}
// 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) {
std::ostringstream oss;
oss.imbue(std::locale::classic());
oss << std::setprecision(std::numeric_limits::digits10) << d;
const std::string str = oss.str();
oss.str("");
std::string::size_type e = str.find('e');
if (e == std::string::npos) {
e = str.find('E');
}
const std::string mantissa = str.substr(0, e);
oss << mantissa;
if (mantissa.find('.') == std::string::npos) {
oss << ".";
}
if (e != std::string::npos) {
oss << "E";
oss << str.substr(e + 1);
}
return oss.str();
}
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 int& i) { data_ << i; }
void operator()(const bool& i) { data_ << (i ? ".T." : ".F."); }
void operator()(const boost::logic::tribool& i) { data_ << (i ? ".T." : (boost::logic::indeterminate(i) ? ".U." : ".F.")); }
void operator()(const double& i) { data_ << format_double(i); }
void operator()(const boost::dynamic_bitset<>& i) { data_ << format_binary(i); }
void operator()(const std::string& i) {
std::string s = i;
if (upper_) {
data_ << static_cast(character_encoder(s));
} else {
data_ << '\'' << s << '\'';
}
}
void operator()(const std::vector& i);
void operator()(const std::vector& i);
void operator()(const std::vector& i);
void operator()(const std::vector>& i);
void operator()(const enumeration_reference& i) {
data_ << "." << i.value() << ".";
}
void operator()(const express::Base& i) {
if (i.declaration().as_entity() == nullptr || i.declaration().schema() == &Header_section_schema::get_schema()) {
i.to_string(data_, upper_);
} else {
data_ << "#" << i.id();
}
}
void operator()(const std::vector& i) {
data_ << "(";
for (auto it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
(*this)(*it);
}
data_ << ")";
}
void operator()(const std::vector>& i);
void operator()(const std::vector>& i);
void operator()(const std::vector>& i) {
data_ << "(";
for (auto outer_it = i.begin(); outer_it != i.end(); ++outer_it) {
if (outer_it != i.begin()) {
data_ << ",";
}
data_ << "(";
for (auto inner_it = outer_it->begin(); inner_it != outer_it->end(); ++inner_it) {
if (inner_it != outer_it->begin()) {
data_ << ",";
}
(*this)(*inner_it);
}
data_ << ")";
}
data_ << ")";
}
void operator()(const empty_aggregate_t& /*unused*/) const { data_ << "()"; }
void operator()(const empty_aggregate_of_aggregate_t& /*unused*/) const { data_ << "()"; }
};
template <>
void StringBuilderVisitor::serialize(const std::vector& i) {
data_ << "(";
for (std::vector::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
std::string encoder = character_encoder(*it);
data_ << encoder;
}
data_ << ")";
}
template <>
void StringBuilderVisitor::serialize(const std::vector& i) {
data_ << "(";
for (std::vector::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
data_ << format_double(*it);
}
data_ << ")";
}
template <>
void StringBuilderVisitor::serialize(const std::vector>& i) {
data_ << "(";
for (std::vector>::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
data_ << format_binary(*it);
}
data_ << ")";
}
void StringBuilderVisitor::operator()(const std::vector& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector>& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector>& i) {
data_ << "(";
for (std::vector>::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
serialize(*it);
}
data_ << ")";
}
void StringBuilderVisitor::operator()(const std::vector>& i) {
data_ << "(";
for (std::vector>::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
serialize(*it);
}
data_ << ")";
}
}
//
// Returns a string representation of the entity
// Note that this initializes the entity if it is not initialized
//
void instance_data::to_string(std::ostream& ss, bool upper) const {
ss.imbue(std::locale::classic());
ss << "(";
StringBuilderVisitor vis(ss, upper);
// In almost all cases, storage is initialized with the size of the schema declaration,
// apparently except in case of header entities and invalid in-line type declarations.
auto size = (declaration_ && declaration_->as_entity() ? declaration_->as_entity()->attribute_count() : 1);
if (storage_) {
size = (std::min)(size, storage_->size());
}
for (size_t i = 0; i < size; ++i) {
if (i != 0) {
ss << ",";
}
if (has_attribute_value(i)) {
if (declaration_ != nullptr && declaration_->as_entity() && declaration_->as_entity()->derived()[i]) {
ss << "*";
} else {
ss << "$";
}
} else {
get_attribute_value(i).apply_visitor(vis);
}
}
ss << ")";
}
/*
unsigned ifcopenshell::IfcBaseEntity::set_id(const std::optional& i) {
if (i) {
return id_ = *i;
}
return id_ = file_->fresh_id();
}
*/
namespace {
// @todo remove redundancy with python wrapper code (which is not identical due to
// different handling of enumerations)
ifcopenshell::argument_type get_argument_type(const ifcopenshell::declaration* decl, size_t i) {
const ifcopenshell::parameter_type* pt = 0;
if (decl->as_entity() != nullptr) {
pt = decl->as_entity()->attribute_by_index(i)->type_of_attribute();
if (decl->as_entity()->derived()[i]) {
return ifcopenshell::Argument_DERIVED;
}
} else if ((decl->as_type_declaration() != nullptr) && i == 0) {
pt = decl->as_type_declaration()->declared_type();
} else if ((decl->as_enumeration_type() != nullptr) && i == 0) {
return ifcopenshell::Argument_ENUMERATION;
}
if (pt == 0) {
return ifcopenshell::Argument_UNKNOWN;
}
return ifcopenshell::from_parameter_type(pt);
}
} // namespace
class unregister_inverse_visitor {
private:
file& file_;
const express::Base data_;
public:
unregister_inverse_visitor(file& file, const express::Base& data)
: file_(file),
data_(data) {}
void operator()(const express::Base& inst, int index) {
file_.unregister_inverse(data_.id(), data_.declaration().as_entity(), inst, index);
}
};
class register_inverse_visitor {
private:
file& file_;
const express::Base data_;
public:
register_inverse_visitor(file& file, const express::Base& data)
: file_(file),
data_(data) {}
void operator()(const express::Base& inst, int index) {
file_.register_inverse(data_.id(), data_.declaration().as_entity(), inst.id(), index);
}
};
class add_to_instance_list_visitor {
private:
std::vector* list_;
public:
add_to_instance_list_visitor(std::vector* list)
: list_(list) {}
void operator()(const express::Base& inst) {
list_->push_back(inst);
}
};
class apply_individual_instance_visitor {
private:
std::optional attribute_;
int attribute_index_;
const express::Base inst_;
template
void apply_attribute_(T& t, const attribute_value& attr, int index) const {
switch (attr.type()) {
case ifcopenshell::Argument_ENTITY_INSTANCE: {
express::Base inst = attr;
t(inst, index);
break;
}
case ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE: {
std::vector entity_list_attribute = attr;
for (auto& inst : entity_list_attribute) {
t(inst, index);
}
break;
}
case ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE: {
std::vector> nested_list_attr = attr;
for (auto& vec : nested_list_attr) {
for (auto& inst : vec) {
t(inst, index);
}
}
break;
}
default:
break;
}
}
public:
apply_individual_instance_visitor(const attribute_value& attribute, int idx)
: attribute_(attribute)
, attribute_index_(idx)
{}
apply_individual_instance_visitor(const express::Base& data)
: inst_(data)
{}
template
void apply(T& t) const {
if (attribute_) {
apply_attribute_(t, *attribute_, attribute_index_);
} else {
const auto& decl = inst_.declaration();
for (size_t i = 0; i < (decl.as_entity() ? decl.as_entity()->attribute_count() : 1); ++i) {
auto attr = inst_.get_attribute_value(i);
apply_attribute_(t, attr, (int) i);
}
}
};
};
template
typename std::enable_if<
(!std::is_base_of_v || std::is_same_v),
void>::type
express::Base::set_attribute_value(size_t i, const T& t) {
if constexpr (std::is_same_v, double>) {
if (!std::isfinite(t)) {
throw ifcopenshell::exception("Only finite values are allowed");
}
}
if constexpr (std::is_same_v, std::vector>) {
if (std::any_of(t.begin(), t.end(), [](double d) { return !std::isfinite(d); })) {
throw ifcopenshell::exception("Only finite values are allowed");
}
}
if constexpr (std::is_same_v, std::vector>>) {
for (auto& tt : t) {
if (std::any_of(tt.begin(), tt.end(), [](double d) { return !std::isfinite(d); })) {
throw ifcopenshell::exception("Only finite values are allowed");
}
}
}
auto current_attribute = get_attribute_value(i);
// Deregister old attribute guid in file guid map.
if (i == 0 && (file()->ifcroot_type() != nullptr) && this->declaration().is(*file()->ifcroot_type())) {
try {
auto guid = (std::string) current_attribute;
auto it = file()->internal_guid_map().find(guid);
if (it != file()->internal_guid_map().end()) {
const std::pair& p = *it;
if (p.second == *this) {
file()->internal_guid_map().erase(it);
}
}
} catch (ifcopenshell::exception& e) {
logger::error(e);
}
}
if constexpr (std::is_same_v || std::is_same_v> || std::is_same_v>> || std::is_same_v) {
// Deregister inverse indices in file
unregister_inverse_visitor visitor(*file(), *this);
apply_individual_instance_visitor(current_attribute, (int)i).apply(visitor);
}
{
void* const storage = std::visit([](const auto& m) { return (void*)&m; }, file()->storage_);
data()->set_attribute_value(i, t);
}
auto new_attribute = get_attribute_value(i);
// Register inverse indices in file
if constexpr (std::is_same_v || std::is_same_v> || std::is_same_v>>) {
register_inverse_visitor visitor(*file(), *this);
apply_individual_instance_visitor(new_attribute, (int)i).apply(visitor);
}
// Register new attribute guid in guid map
if (i == 0 && (file()->ifcroot_type() != nullptr) && this->declaration().is(*file()->ifcroot_type())) {
try {
auto guid = (std::string) new_attribute;
auto it = file()->internal_guid_map().find(guid);
if (it != file()->internal_guid_map().end()) {
logger::warning("Duplicate guid " + guid);
}
file()->internal_guid_map().insert({guid, *this});
} catch (ifcopenshell::exception& e) {
logger::error(e);
}
}
}
template
typename std::enable_if<
(!std::is_base_of_v || std::is_same_v),
void>::type
express::Base::set_attribute_value(const std::string& s, const T& t)
{
set_attribute_value(declaration().as_entity()->attribute_index(s), t);
}
//
// Parses the IFC file in fn
// Creates the maps
//
#ifdef USE_MMAP
file::file(const std::string& fn, bool mmap) {
initialize(fn, mmap);
}
bool ifcopenshell::file::initialize(const std::string& fn, bool mmap) {
if (mmap) {
file_reader s(fn);
storage_.emplace<1>(this);
std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
} else {
file_reader s(fn);
storage_.emplace<1>(this);
std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
}
if ((good_ = std::get(storage_).good_)) {
// @todo unify these names, it's already confusing enough as it stands
byid_ = decltype(byid_)(&std::get(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
}
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
return good_ == file_open_status::SUCCESS;
}
#endif
file::file(const uninitialized_tag&)
: schema_(nullptr), max_id_(0), header_(new ifcopenshell::spf_header(this)), good_(file_open_status::UNKNOWN), ifcroot_type_(nullptr) {}
bool ifcopenshell::file::initialize(const std::string& path, filetype ty, bool readonly) {
if (ty == FT_AUTODETECT) {
ty = guess_file_type(path);
}
if (ty == FT_IFCSPF) {
file_reader s(path);
storage_.emplace<1>(this);
std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
if ((good_ = std::get(storage_).good_)) {
// @todo unify these names, it's already confusing enough as it stands
byid_ = decltype(byid_)(&std::get(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
}
// byidentity_ = decltype(byidentity_)(&std::get(storage_).byidentity_);
} else if (ty == FT_ROCKSDB) {
// This would make some difference, but in the greater light of things, not really significant
// LateBoundEntity is also still large per instance
// instantiate_typed_instances = false;
// @todo this can only be used for databases that already exist, because otherwise there is no way to specify the schema
storage_.emplace<2>(path, this, readonly);
if (std::get(storage_).db == nullptr) {
storage_.emplace<0>();
good_ = file_open_status::READ_ERROR;
} else {
if (std::get(storage_).read_schema(schema_)) {
byid_ = decltype(byid_)(&std::get(storage_).instance_by_name_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
} else {
good_ = file_open_status::UNSUPPORTED_SCHEMA;
}
}
// byidentity_ = decltype(byidentity_)(&std::get(storage_).instance_cache_);
} else {
storage_.emplace<0>();
good_ = file_open_status::READ_ERROR;
// throw std::runtime_error("Unsupported file format");
}
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
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)
: schema_(nullptr)
, max_id_(0)
, header_(new spf_header(this))
{
initialize(path, ty, readonly);
}
file::file(std::istream& stream, int length)
: schema_(nullptr)
, max_id_(0)
, header_(new ifcopenshell::spf_header(this))
{
file_reader s(caller_fed_tag{});
std::string string_data;
string_data.resize(length);
stream.read(string_data.data(), length);
s.push_next_page(string_data);
storage_.emplace<1>(this);
std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
good_ = std::get(storage_).good_;
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
byid_ = decltype(byid_)(&std::get(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
}
file::file(void* data, int length)
: schema_(nullptr)
, max_id_(0),
header_(new ifcopenshell::spf_header(this))
{
file_reader s(std::string((char*)data, length), caller_fed_tag{});
storage_.emplace<1>(this);
std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
good_ = std::get(storage_).good_;
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
byid_ = decltype(byid_)(&std::get(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
}
file::file(const ifcopenshell::schema_definition* schema, filetype ty, const std::string& path)
: 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);
byid_ = decltype(byid_)(&std::get(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
// byidentity_ = decltype(byidentity_)(&std::get(storage_).byidentity_);
} else if (ty == FT_ROCKSDB) {
storage_.emplace<2>(path, this);
byid_ = decltype(byid_)(&std::get(storage_).instance_by_name_);
byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get(storage_).byguid_);
// byidentity_ = decltype(byidentity_)(&std::get(storage_).instance_cache_);
} else {
throw std::runtime_error("Unsupported file format");
}
header_.reset(new spf_header(this));
set_default_header_values();
}
namespace {
template
void read_terminal(spf_lexer& lexer, const std::string& term, bool trailing_semicolon) {
if (lexer.next().as_string() != term) {
throw exception(std::string("Expected " + term));
}
if (trailing_semicolon) {
if (!lexer.next().is_operator(';')) {
throw exception("Expected ;");
}
}
}
template
std::shared_ptr read_header_entity(
ifcopenshell::file* file,
ifcopenshell::impl::in_memory_file_storage& storage,
spf_lexer& lexer,
ifcopenshell::unresolved_references& references_to_resolve,
const ifcopenshell::entity& decl) {
parse_context pc(&storage.context_pool_);
lexer.next();
storage.load(&lexer, std::nullopt, nullptr, pc, -1);
auto result = pc.construct(file, std::nullopt, references_to_resolve, &decl, decl.attribute_count(), -1);
storage.context_pool_.reset();
return result;
}
template
void parse_header(
ifcopenshell::spf_header& header,
ifcopenshell::impl::in_memory_file_storage& storage,
spf_lexer& lexer,
ifcopenshell::unresolved_references& references_to_resolve) {
static const char* const ISO_10303_21 = "ISO-10303-21";
static const char* const HEADER = "HEADER";
read_terminal(lexer, ISO_10303_21, true);
read_terminal(lexer, HEADER, true);
read_terminal(lexer, Header_section_schema::file_description::Class().name_uc(), false);
header.set_file_description(read_header_entity(header.owner_file(), storage, lexer, references_to_resolve, Header_section_schema::file_description::Class()));
if (!lexer.next().is_operator(';')) {
throw exception("Expected ;");
}
read_terminal(lexer, Header_section_schema::file_name::Class().name_uc(), false);
header.set_file_name(read_header_entity(header.owner_file(), storage, lexer, references_to_resolve, Header_section_schema::file_name::Class()));
if (!lexer.next().is_operator(';')) {
throw exception("Expected ;");
}
read_terminal(lexer, Header_section_schema::file_schema::Class().name_uc(), false);
header.set_file_schema(read_header_entity(header.owner_file(), storage, lexer, references_to_resolve, Header_section_schema::file_schema::Class()));
if (!lexer.next().is_operator(';')) {
throw exception("Expected ;");
}
}
template
bool try_parse_header(
ifcopenshell::spf_header& header,
ifcopenshell::impl::in_memory_file_storage& storage,
spf_lexer& lexer,
ifcopenshell::unresolved_references& references_to_resolve) {
try {
parse_header(header, storage, lexer, references_to_resolve);
return true;
} catch (const std::exception& e) {
storage.context_pool_.reset();
logger::error(e);
return false;
}
}
} // namespace
template
spf_header& ifcopenshell::instance_streamer::ensure_header() {
if (header_) {
return *header_;
}
if (owner_ != nullptr) {
header_ = &owner_->header();
header_->owner_file(owner_);
} else {
owned_header_ = std::make_unique(owner_);
header_ = owned_header_.get();
}
return *header_;
}
template
void ifcopenshell::instance_streamer::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_;
}
template
bool ifcopenshell::instance_streamer::has_semicolon() const {
auto local_stream = stream_->clone();
auto local_lexer = spf_lexer(&local_stream);
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
size_t ifcopenshell::instance_streamer::semicolon_count() const {
auto local_stream = stream_->clone();
auto local_lexer = spf_lexer(&local_stream);
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
void ifcopenshell::instance_streamer::push_page(const std::string& page) {
stream_->push_next_page(page);
if (good_ == file_open_status::NO_HEADER) {
initialize_header();
}
}
template
ifcopenshell::instance_streamer::instance_streamer(ifcopenshell::file* f)
: stream_(nullptr)
, header_(nullptr)
, owner_(f)
, token_stream_(3, token{})
, schema_(nullptr)
, progress_(0)
{
init_locale();
if constexpr (std::is_same_v>) {
owned_stream_ = std::make_unique(caller_fed_tag{});
} else if constexpr (std::is_same_v>) {
owned_stream_ = std::make_unique(caller_fed_tag{});
} else {
static_assert(file_reader_dependent_false_v, "Default instance_streamer requires a pushed sequential reader");
}
stream_ = owned_stream_.get();
lexer_ = std::make_unique>(stream_);
good_ = file_open_status::NO_HEADER;
storage_.file = f;
storage_.references_to_resolve = &references_to_resolve_;
}
template
ifcopenshell::instance_streamer::instance_streamer(const std::string& fn, bool mmap, ifcopenshell::file* f)
: stream_(nullptr)
, header_(nullptr)
, owner_(f)
, token_stream_(3, token{})
, schema_(nullptr)
, progress_(0) {
init_locale();
if constexpr (std::is_same_v>) {
(void)mmap;
owned_stream_ = std::make_unique(fn);
#ifdef USE_MMAP
} else if constexpr (std::is_same_v>) {
(void)mmap;
owned_stream_ = std::make_unique(fn);
#endif
} else {
static_assert(file_reader_dependent_false_v, "Path-based instance_streamer requires a file-backed reader");
}
stream_ = owned_stream_.get();
lexer_ = std::make_unique>(stream_);
good_ = file_open_status::NO_HEADER;
initialize_header();
}
template
ifcopenshell::instance_streamer::instance_streamer(void* data, int length, ifcopenshell::file* f)
: stream_(nullptr)
, header_(nullptr)
, owner_(f)
, token_stream_(3, token{})
, schema_(nullptr)
, progress_(0)
{
init_locale();
if constexpr (std::is_same_v>) {
owned_stream_ = std::make_unique(std::string((char*)data, length), caller_fed_tag{});
} else if constexpr (std::is_same_v>) {
owned_stream_ = std::make_unique(std::string((char*)data, length), caller_fed_tag{});
} else {
static_assert(file_reader_dependent_false_v, "Buffer-based instance_streamer requires a pushed sequential reader");
}
stream_ = owned_stream_.get();
lexer_ = std::make_unique>(stream_);
good_ = file_open_status::NO_HEADER;
initialize_header();
}
template
ifcopenshell::instance_streamer::instance_streamer(Reader* stream, ifcopenshell::file* f)
: stream_(stream)
, header_(nullptr)
, owner_(f)
, token_stream_(3, token{})
, schema_(nullptr)
, progress_(0) {
init_locale();
lexer_ = std::make_unique>(stream_);
good_ = file_open_status::NO_HEADER;
initialize_header();
}
template
void ifcopenshell::instance_streamer::bypass_types(const std::set& type_names) {
for (auto& name : type_names) {
try {
types_to_bypass_.push_back(schema_->declaration_by_name(name));
} catch (const exception&) {
continue;
}
}
}
template
std::optional>> ifcopenshell::instance_streamer::read_instance() {
std::optional>> return_value;
if (yield_header_instances_ && header_ && yielded_header_instances_ < 3) {
if (yielded_header_instances_ == 0) {
return_value.emplace(
0,
&header_->file_description().declaration(),
header_->file_description().data_weak().lock());
} else if (yielded_header_instances_ == 1) {
return_value.emplace(
0,
&header_->file_name().declaration(),
header_->file_name().data_weak().lock());
} else if (yielded_header_instances_ == 2) {
return_value.emplace(
0,
&header_->file_schema().declaration(),
header_->file_schema().data_weak().lock());
}
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::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::message(logger::LOG_ERROR, "Non entity type " + entity_type->name() + " at offset " + std::to_string(token_stream_[2].start_pos));
goto advance;
}
for (auto& ty : types_to_bypass_) {
if (entity_type->is(*ty)) {
bypassed_instances_.push_back(current_id);
current_id = 0;
goto advance;
}
}
parse_context ps(&storage_.context_pool_);
lexer_->next();
try {
storage_.load(lexer_.get(), current_id, entity_type->as_entity(), ps, -1);
} catch (const invalid_token_exception& e) {
good_ = file_open_status::INVALID_SYNTAX;
logger::error(e);
break;
}
if (((++progress_) % 1000) == 0) {
std::stringstream ss;
ss << "\r#" << current_id;
logger::status(ss.str(), false);
}
auto data = ps.construct(owner_, current_id, references_to_resolve_, entity_type, std::nullopt, -1, coerce_attribute_count);
storage_.context_pool_.reset();
return_value.emplace(
(size_t)current_id,
entity_type,
data);
}
advance:
token next_token;
try {
next_token = lexer_->next();
} catch (const exception& e) {
logger::message(logger::LOG_ERROR, std::string(e.what()) + ". Parsing terminated");
} catch (...) {
logger::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 ifcopenshell::instance_streamer>;
template
void ifcopenshell::impl::in_memory_file_storage::read_from_stream(Reader* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set& typed_to_bypass) {
init_locale();
schema = nullptr;
if (!s->size() || s->eof()) {
good_ = file_open_status::READ_ERROR;
return;
}
std::vector schemas;
instance_streamer streamer(s, file);
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::error(e);
}
}
if (schema == nullptr) {
if (schemas.empty()) {
good_ = streamer.status();
} else {
good_ = file_open_status::UNSUPPORTED_SCHEMA;
}
logger::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::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::message(logger::LOG_WARNING, ss.str());
}
byguid_[guid] = instance;
} catch (const exception& ex) {
logger::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::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_ = streamer.inverses();
read_simple_type_instances = streamer.steal_instances();
logger::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_;
if (auto* v = std::get_if(&p.second)) {
if (auto* name = std::get_if(v)) {
if (std::binary_search(bypassed.begin(), bypassed.end(), *name)) {
continue;
}
auto it = byid_.find(*name);
if (it == byid_.end()) {
logger::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 = byid_[p.first.name_];
auto attr_index = p.first.index_;
if (storage->template has_attribute_value(attr_index)) {
express::Base inst = storage->get_attribute_value(attr_index);
if (!inst.declaration().as_entity()) {
// Probably a case of IfcPropertySetDefinitionSet, divert storage of reference to the simply type instance
storage = inst.data_weak().lock();
attr_index = 0;
}
}
if (storage->template has_attribute_value(attr_index)) {
storage->set_attribute_value(attr_index, express::Base(it->second));
} else {
logger::error("Duplicate definition for instance reference");
}
}
} else if (auto inst = std::get_if(v)) {
byid_[p.first.name_]->set_attribute_value(p.first.index_, *inst);
}
} else if (auto* vv = std::get_if>(&p.second)) {
std::vector instances;
instances.reserve(vv->size());
for (const auto& vi : *vv) {
if (auto* name = std::get_if(&vi)) {
if (std::binary_search(bypassed.begin(), bypassed.end(), *name)) {
continue;
}
auto it = byid_.find(*name);
if (it == byid_.end()) {
logger::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(&vi)) {
instances.push_back(*inst);
}
}
auto& storage = byid_[p.first.name_];
auto attr_index = p.first.index_;
if (storage->template has_attribute_value(attr_index)) {
express::Base inst = storage->get_attribute_value(attr_index);
if (!inst.declaration().as_entity()) {
// Probably a case of IfcPropertySetDefinitionSet, divert storage of reference to the simply type instance
storage = inst.data_weak().lock();
attr_index = 0;
}
}
if (storage->template has_attribute_value(attr_index)) {
storage->set_attribute_value(attr_index, instances);
} else {
logger::error("Duplicate definition for instance reference");
}
} else if (auto* vvv = std::get_if>>(&p.second)) {
std::vector> instances;
for (const auto& vi : *vvv) {
auto& inner = instances.emplace_back();
for (const auto& vii : vi) {
if (auto* name = std::get_if(&vii)) {
if (std::binary_search(bypassed.begin(), bypassed.end(), *name)) {
continue;
}
auto it = byid_.find(*name);
if (it == byid_.end()) {
logger::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(&vii)) {
inner.push_back(*inst);
}
}
}
auto& storage = byid_[p.first.name_];
auto attr_index = p.first.index_;
if (storage->template has_attribute_value(attr_index)) {
express::Base inst = storage->get_attribute_value(attr_index);
if (!inst.declaration().as_entity()) {
// Probably a case of IfcPropertySetDefinitionSet, divert storage of reference to the simply type instance
storage = inst.data_weak().lock();
attr_index = 0;
}
}
if (storage->template has_attribute_value(attr_index)) {
storage->set_attribute_value(attr_index, instances);
} else {
logger::error("Duplicate definition for instance reference");
}
}
}
logger::status("Done resolving references");
}
template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(file_reader* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set& typed_to_bypass);
template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(file_reader* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set& typed_to_bypass);
#ifdef USE_MMAP
template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(file_reader* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set& 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 list_;
std::map> 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 get_list() const {
if (mode_ == 0) {
return list_;
}
std::vector 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& visited_;
traversal_recorder& list_;
int level_;
int max_level_;
public:
traversal_visitor(std::set& 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& 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 ifcopenshell::traverse(const express::Base& instance, int max_level) {
std::set 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 ifcopenshell::traverse_breadth_first(const express::Base& instance, int max_level) {
std::set visited;
traversal_recorder recorder(1);
traverse_(instance, visited, recorder, 0, max_level);
return recorder.get_list();
}
/// @note: for backwards compatibility
std::vector