mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 06:39:13 +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
|
import ifcopenshell
|
||||||
|
|
||||||
|
|
||||||
@@ -21,7 +23,9 @@ END-ISO-10303-21;
|
|||||||
|
|
||||||
|
|
||||||
def test_reference_to_undefined_owning_instance():
|
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)
|
ifcopenshell.file.from_string(data)
|
||||||
print(ifcopenshell.get_log())
|
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("
|
data = "ISO-10303-21;HEADER;FILE_DESCRIPTION();FILE_NAME();FILE_SCHEMA(('IFC4'));#=IFCPROJECT((#))#4=IFCSIUNIT("
|
||||||
ifcopenshell.file.from_string(data)
|
ifcopenshell.file.from_string(data)
|
||||||
print(ifcopenshell.get_log())
|
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 "utils.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/variant.hpp>
|
#include <boost/variant.hpp>
|
||||||
#include <boost/math/special_functions/fpclassify.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);
|
double result = strtod_l(pStart, &pEnd, locale);
|
||||||
#endif
|
#endif
|
||||||
if (*pEnd != 0) {
|
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;
|
val = result;
|
||||||
return true;
|
return true;
|
||||||
@@ -339,6 +346,9 @@ Token IfcParse::GeneralTokenPtr(IfcSpfLexer* lexer, size_t start, const std::str
|
|||||||
token.type = Token_INT;
|
token.type = Token_INT;
|
||||||
} else if (ParseFloat(tokenStr.c_str(), token.value_double)) {
|
} else if (ParseFloat(tokenStr.c_str(), token.value_double)) {
|
||||||
token.type = Token_FLOAT;
|
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 {
|
} else {
|
||||||
token.type = Token_KEYWORD;
|
token.type = Token_KEYWORD;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user