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