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