mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
IfcParse: read back non-finite REALs serialized with a trailing dot #6409
Legacy releases serialized non-finite doubles by appending a '.' to the formatted value, producing tokens like "nan.", "-nan(ind)." and "inf." that strtod stops in front of. The tokenizer then classified them as schema keywords, and after the #4070 recovery the argument was silently dropped, leaving zero-argument typed values (e.g. IfcReal()) that raised cryptic RuntimeErrors from get_psets and ifccsv on any attribute access. Accept a single trailing '.' after a successful partial strtod parse so these files load again, and log a parse error for every non-finite REAL so ifcopenshell.validate surfaces them when validating from a path. Writing non-finite values remains blocked ("Only finite values are allowed"). Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import math
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
@@ -21,7 +23,9 @@ END-ISO-10303-21;
|
||||
|
||||
|
||||
def test_reference_to_undefined_owning_instance():
|
||||
data = "ISO-10303-21;HEADER;FILE_DESCRIPTION();FILE_NAME();FILE_SCHEMA(('IFC4'));#=IFCRELAGGREGATES((#))#5=IFCPOINT)"
|
||||
data = (
|
||||
"ISO-10303-21;HEADER;FILE_DESCRIPTION();FILE_NAME();FILE_SCHEMA(('IFC4'));#=IFCRELAGGREGATES((#))#5=IFCPOINT)"
|
||||
)
|
||||
ifcopenshell.file.from_string(data)
|
||||
print(ifcopenshell.get_log())
|
||||
|
||||
@@ -30,3 +34,29 @@ def test_reference_to_undefined_owning_instance_simple_type():
|
||||
data = "ISO-10303-21;HEADER;FILE_DESCRIPTION();FILE_NAME();FILE_SCHEMA(('IFC4'));#=IFCPROJECT((#))#4=IFCSIUNIT("
|
||||
ifcopenshell.file.from_string(data)
|
||||
print(ifcopenshell.get_log())
|
||||
|
||||
|
||||
def test_non_finite_reals_with_trailing_dot():
|
||||
# Legacy serialization appended '.' to non-finite doubles ("nan.", "inf."),
|
||||
# which tokenized as unknown keywords and left zero-argument typed values
|
||||
# that raised on attribute access (#6409).
|
||||
data = """
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((''),'2;1');
|
||||
FILE_NAME('','',(''),(''),'','','');
|
||||
FILE_SCHEMA(('IFC2X3'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSINGLEVALUE('test',$,IFCREAL(nan.),$);
|
||||
#2=IFCCARTESIANPOINT((0.,inf.,-inf.));
|
||||
#3=IFCDIRECTION((-nan.,-nan.,-nan.));
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
"""
|
||||
f = ifcopenshell.file.from_string(data)
|
||||
log = ifcopenshell.get_log()
|
||||
assert math.isnan(f.by_id(1).NominalValue.wrappedValue)
|
||||
assert f.by_id(2).Coordinates == (0.0, math.inf, -math.inf)
|
||||
assert all(math.isnan(ratio) for ratio in f.by_id(3).DirectionRatios)
|
||||
assert "Non-finite value" in log
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
#include <boost/math/special_functions/fpclassify.hpp>
|
||||
@@ -283,7 +284,13 @@ bool ParseFloat(const char* pStart, double& val) {
|
||||
double result = strtod_l(pStart, &pEnd, locale);
|
||||
#endif
|
||||
if (*pEnd != 0) {
|
||||
return false;
|
||||
// Historically REALs were serialized by appending a '.' to any formatted
|
||||
// double lacking one, producing non-finite spellings such as "nan.",
|
||||
// "-nan(ind)." and "inf." that strtod stops in front of. Accept a single
|
||||
// trailing '.' after a successful partial parse so these load again.
|
||||
if (pEnd == pStart || pEnd[0] != '.' || pEnd[1] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
val = result;
|
||||
return true;
|
||||
@@ -339,6 +346,9 @@ Token IfcParse::GeneralTokenPtr(IfcSpfLexer* lexer, size_t start, const std::str
|
||||
token.type = Token_INT;
|
||||
} else if (ParseFloat(tokenStr.c_str(), token.value_double)) {
|
||||
token.type = Token_FLOAT;
|
||||
if (!std::isfinite(token.value_double)) {
|
||||
lexer->logger().Message(Logger::LOG_ERROR, "SYN", 41, "Non-finite value '" + tokenStr + "' at offset " + std::to_string(token.startPos));
|
||||
}
|
||||
} else {
|
||||
token.type = Token_KEYWORD;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user