1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2017, assimp team
6
7All rights reserved.
8
9Redistribution and use of this software in source and binary forms,
10with or without modification, are permitted provided that the
11following conditions are met:
12
13* Redistributions of source code must retain the above
14 copyright notice, this list of conditions and the
15 following disclaimer.
16
17* Redistributions in binary form must reproduce the above
18 copyright notice, this list of conditions and the
19 following disclaimer in the documentation and/or other
20 materials provided with the distribution.
21
22* Neither the name of the assimp team, nor the names of its
23 contributors may be used to endorse or promote products
24 derived from this software without specific prior
25 written permission of the assimp team.
26
27THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39----------------------------------------------------------------------
40*/
41
42/** @file STEPFileReader.cpp
43 * @brief Implementation of the STEP file parser, which fills a
44 * STEP::DB with data read from a file.
45 */
46
47#include "STEPFileReader.h"
48#include "STEPFileEncoding.h"
49#include "TinyFormatter.h"
50#include "fast_atof.h"
51#include <memory>
52
53
54using namespace Assimp;
55namespace EXPRESS = STEP::EXPRESS;
56
57#include <functional>
58
59// ------------------------------------------------------------------------------------------------
60std::string AddLineNumber(const std::string& s,uint64_t line /*= LINE_NOT_SPECIFIED*/, const std::string& prefix = "")
61{
62 return line == STEP::SyntaxError::LINE_NOT_SPECIFIED ? prefix+s : static_cast<std::string>( (Formatter::format(),prefix,"(line ",line,") ",s) );
63}
64
65
66// ------------------------------------------------------------------------------------------------
67std::string AddEntityID(const std::string& s,uint64_t entity /*= ENTITY_NOT_SPECIFIED*/, const std::string& prefix = "")
68{
69 return entity == STEP::TypeError::ENTITY_NOT_SPECIFIED ? prefix+s : static_cast<std::string>( (Formatter::format(),prefix,"(entity #",entity,") ",s));
70}
71
72
73// ------------------------------------------------------------------------------------------------
74STEP::SyntaxError::SyntaxError (const std::string& s,uint64_t line /* = LINE_NOT_SPECIFIED */)
75: DeadlyImportError(AddLineNumber(s,line))
76{
77
78}
79
80// ------------------------------------------------------------------------------------------------
81STEP::TypeError::TypeError (const std::string& s,uint64_t entity /* = ENTITY_NOT_SPECIFIED */,uint64_t line /*= LINE_NOT_SPECIFIED*/)
82: DeadlyImportError(AddLineNumber(AddEntityID(s,entity),line))
83{
84
85}
86
87
88// ------------------------------------------------------------------------------------------------
89STEP::DB* STEP::ReadFileHeader(std::shared_ptr<IOStream> stream)
90{
91 std::shared_ptr<StreamReaderLE> reader = std::shared_ptr<StreamReaderLE>(new StreamReaderLE(stream));
92 std::unique_ptr<STEP::DB> db = std::unique_ptr<STEP::DB>(new STEP::DB(reader));
93
94 LineSplitter& splitter = db->GetSplitter();
95 if (!splitter || *splitter != "ISO-10303-21;") {
96 throw STEP::SyntaxError("expected magic token: ISO-10303-21",1);
97 }
98
99 HeaderInfo& head = db->GetHeader();
100 for(++splitter; splitter; ++splitter) {
101 const std::string& s = *splitter;
102 if (s == "DATA;") {
103 // here we go, header done, start of data section
104 ++splitter;
105 break;
106 }
107
108 // want one-based line numbers for human readers, so +1
109 const uint64_t line = splitter.get_index()+1;
110
111 if (s.substr(0,11) == "FILE_SCHEMA") {
112 const char* sz = s.c_str()+11;
113 SkipSpaces(sz,&sz);
114 std::shared_ptr< const EXPRESS::DataType > schema = EXPRESS::DataType::Parse(sz);
115
116 // the file schema should be a regular list entity, although it usually contains exactly one entry
117 // since the list itself is contained in a regular parameter list, we actually have
118 // two nested lists.
119 const EXPRESS::LIST* list = dynamic_cast<const EXPRESS::LIST*>(schema.get());
120 if (list && list->GetSize()) {
121 list = dynamic_cast<const EXPRESS::LIST*>( (*list)[0].get() );
122 if (!list) {
123 throw STEP::SyntaxError("expected FILE_SCHEMA to be a list",line);
124 }
125
126 // XXX need support for multiple schemas?
127 if (list->GetSize() > 1) {
128 DefaultLogger::get()->warn(AddLineNumber("multiple schemas currently not supported",line));
129 }
130 const EXPRESS::STRING* string( nullptr );
131 if (!list->GetSize() || !(string=dynamic_cast<const EXPRESS::STRING*>( (*list)[0].get() ))) {
132 throw STEP::SyntaxError("expected FILE_SCHEMA to contain a single string literal",line);
133 }
134 head.fileSchema = *string;
135 }
136 }
137
138 // XXX handle more header fields
139 }
140
141 return db.release();
142}
143
144
145namespace {
146
147// ------------------------------------------------------------------------------------------------
148// check whether the given line contains an entity definition (i.e. starts with "#<number>=")
149bool IsEntityDef(const std::string& snext)
150{
151 if (snext[0] == '#') {
152 // it is only a new entity if it has a '=' after the
153 // entity ID.
154 for(std::string::const_iterator it = snext.begin()+1; it != snext.end(); ++it) {
155 if (*it == '=') {
156 return true;
157 }
158 if ((*it < '0' || *it > '9') && *it != ' ') {
159 break;
160 }
161 }
162 }
163 return false;
164}
165
166}
167
168
169// ------------------------------------------------------------------------------------------------
170void STEP::ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme,
171 const char* const* types_to_track, size_t len,
172 const char* const* inverse_indices_to_track, size_t len2)
173{
174 db.SetSchema(scheme);
175 db.SetTypesToTrack(types_to_track,len);
176 db.SetInverseIndicesToTrack(inverse_indices_to_track,len2);
177
178 const DB::ObjectMap& map = db.GetObjects();
179 LineSplitter& splitter = db.GetSplitter();
180
181 while (splitter) {
182 bool has_next = false;
183 std::string s = *splitter;
184 if (s == "ENDSEC;") {
185 break;
186 }
187 s.erase(std::remove(s.begin(), s.end(), ' '), s.end());
188
189 // want one-based line numbers for human readers, so +1
190 const uint64_t line = splitter.get_index()+1;
191 // LineSplitter already ignores empty lines
192 ai_assert(s.length());
193 if (s[0] != '#') {
194 DefaultLogger::get()->warn(AddLineNumber("expected token \'#\'",line));
195 ++splitter;
196 continue;
197 }
198 // ---
199 // extract id, entity class name and argument string,
200 // but don't create the actual object yet.
201 // ---
202 const std::string::size_type n0 = s.find_first_of('=');
203 if (n0 == std::string::npos) {
204 DefaultLogger::get()->warn(AddLineNumber("expected token \'=\'",line));
205 ++splitter;
206 continue;
207 }
208
209 const uint64_t id = strtoul10_64(s.substr(1,n0-1).c_str());
210 if (!id) {
211 DefaultLogger::get()->warn(AddLineNumber("expected positive, numeric entity id",line));
212 ++splitter;
213 continue;
214 }
215 std::string::size_type n1 = s.find_first_of('(',n0);
216 if (n1 == std::string::npos) {
217 has_next = true;
218 bool ok = false;
219 for( ++splitter; splitter; ++splitter) {
220 const std::string& snext = *splitter;
221 if (snext.empty()) {
222 continue;
223 }
224
225 // the next line doesn't start an entity, so maybe it is
226 // just a continuation for this line, keep going
227 if (!IsEntityDef(snext)) {
228 s.append(snext);
229 n1 = s.find_first_of('(',n0);
230 ok = (n1 != std::string::npos);
231 }
232 else {
233 break;
234 }
235 }
236
237 if(!ok) {
238 DefaultLogger::get()->warn(AddLineNumber("expected token \'(\'",line));
239 continue;
240 }
241 }
242
243 std::string::size_type n2 = s.find_last_of(')');
244 if (n2 == std::string::npos || n2 < n1 || n2 == s.length() - 1 || s[n2 + 1] != ';') {
245
246 has_next = true;
247 bool ok = false;
248 for( ++splitter; splitter; ++splitter) {
249 const std::string& snext = *splitter;
250 if (snext.empty()) {
251 continue;
252 }
253 // the next line doesn't start an entity, so maybe it is
254 // just a continuation for this line, keep going
255 if (!IsEntityDef(snext)) {
256 s.append(snext);
257 n2 = s.find_last_of(')');
258 ok = !(n2 == std::string::npos || n2 < n1 || n2 == s.length() - 1 || s[n2 + 1] != ';');
259 }
260 else {
261 break;
262 }
263 }
264 if(!ok) {
265 DefaultLogger::get()->warn(AddLineNumber("expected token \')\'",line));
266 continue;
267 }
268 }
269
270 if (map.find(id) != map.end()) {
271 DefaultLogger::get()->warn(AddLineNumber((Formatter::format(),"an object with the id #",id," already exists"),line));
272 }
273
274 std::string::size_type ns = n0;
275 do ++ns; while( IsSpace(s.at(ns)));
276 std::string::size_type ne = n1;
277 do --ne; while( IsSpace(s.at(ne)));
278 std::string type = s.substr(ns,ne-ns+1);
279 std::transform( type.begin(), type.end(), type.begin(), &Assimp::ToLower<char> );
280 const char* sz = scheme.GetStaticStringForToken(type);
281 if(sz) {
282 const std::string::size_type len = n2-n1+1;
283 char* const copysz = new char[len+1];
284 std::copy(s.c_str()+n1,s.c_str()+n2+1,copysz);
285 copysz[len] = '\0';
286 db.InternInsert(new LazyObject(db,id,line,sz,copysz));
287 }
288 if(!has_next) {
289 ++splitter;
290 }
291 }
292
293 if (!splitter) {
294 DefaultLogger::get()->warn("STEP: ignoring unexpected EOF");
295 }
296
297 if ( !DefaultLogger::isNullLogger()){
298 DefaultLogger::get()->debug((Formatter::format(),"STEP: got ",map.size()," object records with ",
299 db.GetRefs().size()," inverse index entries"));
300 }
301}
302
303// ------------------------------------------------------------------------------------------------
304std::shared_ptr<const EXPRESS::DataType> EXPRESS::DataType::Parse(const char*& inout,uint64_t line, const EXPRESS::ConversionSchema* schema /*= NULL*/)
305{
306 const char* cur = inout;
307 SkipSpaces(&cur);
308 if (*cur == ',' || IsSpaceOrNewLine(*cur)) {
309 throw STEP::SyntaxError("unexpected token, expected parameter",line);
310 }
311
312 // just skip over constructions such as IFCPLANEANGLEMEASURE(0.01) and read only the value
313 if (schema) {
314 bool ok = false;
315 for(const char* t = cur; *t && *t != ')' && *t != ','; ++t) {
316 if (*t=='(') {
317 if (!ok) {
318 break;
319 }
320 for(--t;IsSpace(*t);--t);
321 std::string s(cur,static_cast<size_t>(t-cur+1));
322 std::transform(s.begin(),s.end(),s.begin(),&ToLower<char> );
323 if (schema->IsKnownToken(s)) {
324 for(cur = t+1;*cur++ != '(';);
325 const std::shared_ptr<const EXPRESS::DataType> dt = Parse(cur);
326 inout = *cur ? cur+1 : cur;
327 return dt;
328 }
329 break;
330 }
331 else if (!IsSpace(*t)) {
332 ok = true;
333 }
334 }
335 }
336
337 if (*cur == '*' ) {
338 inout = cur+1;
339 return std::make_shared<EXPRESS::ISDERIVED>();
340 }
341 else if (*cur == '$' ) {
342 inout = cur+1;
343 return std::make_shared<EXPRESS::UNSET>();
344 }
345 else if (*cur == '(' ) {
346 // start of an aggregate, further parsing is done by the LIST factory constructor
347 inout = cur;
348 return EXPRESS::LIST::Parse(inout,line,schema);
349 }
350 else if (*cur == '.' ) {
351 // enum (includes boolean)
352 const char* start = ++cur;
353 for(;*cur != '.';++cur) {
354 if (*cur == '\0') {
355 throw STEP::SyntaxError("enum not closed",line);
356 }
357 }
358 inout = cur+1;
359 return std::make_shared<EXPRESS::ENUMERATION>(std::string(start, static_cast<size_t>(cur-start) ));
360 }
361 else if (*cur == '#' ) {
362 // object reference
363 return std::make_shared<EXPRESS::ENTITY>(strtoul10_64(++cur,&inout));
364 }
365 else if (*cur == '\'' ) {
366 // string literal
367 const char* start = ++cur;
368
369 for(;*cur != '\'';++cur) {
370 if (*cur == '\0') {
371 throw STEP::SyntaxError("string literal not closed",line);
372 }
373 }
374
375 if (cur[1] == '\'') {
376 // Vesanen: more than 2 escaped ' in one literal!
377 do {
378 for(cur += 2;*cur != '\'';++cur) {
379 if (*cur == '\0') {
380 throw STEP::SyntaxError("string literal not closed",line);
381 }
382 }
383 }
384 while(cur[1] == '\'');
385 }
386
387 inout = cur + 1;
388
389 // assimp is supposed to output UTF8 strings, so we have to deal
390 // with foreign encodings.
391 std::string stemp = std::string(start, static_cast<size_t>(cur - start));
392 if(!StringToUTF8(stemp)) {
393 // TODO: route this to a correct logger with line numbers etc., better error messages
394 DefaultLogger::get()->error("an error occurred reading escape sequences in ASCII text");
395 }
396
397 return std::make_shared<EXPRESS::STRING>(stemp);
398 }
399 else if (*cur == '\"' ) {
400 throw STEP::SyntaxError("binary data not supported yet",line);
401 }
402
403 // else -- must be a number. if there is a decimal dot in it,
404 // parse it as real value, otherwise as integer.
405 const char* start = cur;
406 for(;*cur && *cur != ',' && *cur != ')' && !IsSpace(*cur);++cur) {
407 if (*cur == '.') {
408 double f;
409 inout = fast_atoreal_move<double>(start,f);
410 return std::make_shared<EXPRESS::REAL>(f);
411 }
412 }
413
414 bool neg = false;
415 if (*start == '-') {
416 neg = true;
417 ++start;
418 }
419 else if (*start == '+') {
420 ++start;
421 }
422 int64_t num = static_cast<int64_t>( strtoul10_64(start,&inout) );
423 return std::make_shared<EXPRESS::INTEGER>(neg?-num:num);
424}
425
426
427// ------------------------------------------------------------------------------------------------
428std::shared_ptr<const EXPRESS::LIST> EXPRESS::LIST::Parse(const char*& inout,uint64_t line, const EXPRESS::ConversionSchema* schema /*= NULL*/)
429{
430 const std::shared_ptr<EXPRESS::LIST> list = std::make_shared<EXPRESS::LIST>();
431 EXPRESS::LIST::MemberList& members = list->members;
432
433 const char* cur = inout;
434 if (*cur++ != '(') {
435 throw STEP::SyntaxError("unexpected token, expected \'(\' token at beginning of list",line);
436 }
437
438 // estimate the number of items upfront - lists can grow large
439 size_t count = 1;
440 for(const char* c=cur; *c && *c != ')'; ++c) {
441 count += (*c == ',' ? 1 : 0);
442 }
443
444 members.reserve(count);
445
446 for(;;++cur) {
447 if (!*cur) {
448 throw STEP::SyntaxError("unexpected end of line while reading list");
449 }
450 SkipSpaces(cur,&cur);
451 if (*cur == ')') {
452 break;
453 }
454
455 members.push_back( EXPRESS::DataType::Parse(cur,line,schema));
456 SkipSpaces(cur,&cur);
457
458 if (*cur != ',') {
459 if (*cur == ')') {
460 break;
461 }
462 throw STEP::SyntaxError("unexpected token, expected \',\' or \')\' token after list element",line);
463 }
464 }
465
466 inout = cur+1;
467 return list;
468}
469
470
471// ------------------------------------------------------------------------------------------------
472STEP::LazyObject::LazyObject(DB& db, uint64_t id,uint64_t /*line*/, const char* const type,const char* args)
473 : id(id)
474 , type(type)
475 , db(db)
476 , args(args)
477 , obj()
478{
479 // find any external references and store them in the database.
480 // this helps us emulate STEPs INVERSE fields.
481 if (db.KeepInverseIndicesForType(type)) {
482 const char* a = args;
483
484 // do a quick scan through the argument tuple and watch out for entity references
485 int64_t skip_depth = 0;
486 while(*a) {
487 if (*a == '(') {
488 ++skip_depth;
489 }
490 else if (*a == ')') {
491 --skip_depth;
492 }
493
494 if (skip_depth >= 1 && *a=='#') {
495 const char* tmp;
496 const int64_t num = static_cast<int64_t>( strtoul10_64(a+1,&tmp) );
497 db.MarkRef(num,id);
498 }
499 ++a;
500 }
501
502 }
503}
504
505// ------------------------------------------------------------------------------------------------
506STEP::LazyObject::~LazyObject()
507{
508 // make sure the right dtor/operator delete get called
509 if (obj) {
510 delete obj;
511 }
512 else delete[] args;
513}
514
515// ------------------------------------------------------------------------------------------------
516void STEP::LazyObject::LazyInit() const
517{
518 const EXPRESS::ConversionSchema& schema = db.GetSchema();
519 STEP::ConvertObjectProc proc = schema.GetConverterProc(type);
520
521 if (!proc) {
522 throw STEP::TypeError("unknown object type: " + std::string(type),id);
523 }
524
525 const char* acopy = args;
526 std::shared_ptr<const EXPRESS::LIST> conv_args = EXPRESS::LIST::Parse(acopy,STEP::SyntaxError::LINE_NOT_SPECIFIED,&db.GetSchema());
527 delete[] args;
528 args = NULL;
529
530 // if the converter fails, it should throw an exception, but it should never return NULL
531 try {
532 obj = proc(db,*conv_args);
533 }
534 catch(const TypeError& t) {
535 // augment line and entity information
536 throw TypeError(t.what(),id);
537 }
538 ++db.evaluated_count;
539 ai_assert(obj);
540
541 // store the original id in the object instance
542 obj->SetID(id);
543}
544
545