1 
2 #include <errordesc.h>
3 #include <stdio.h>
4 #include <sdai.h>
5 #include <read_func.h>
6 #include <STEPattribute.h>
7 #include "Str.h"
8 #include "sc_memmgr.h"
9 
10 const int RealNumPrecision = REAL_NUM_PRECISION;
11 
12 // print Error information for debugging purposes
13 void
PrintErrorState(ErrorDescriptor & err)14 PrintErrorState( ErrorDescriptor & err ) {
15     cout << "** severity: ";
16     switch( err.severity() ) {
17         case SEVERITY_NULL :
18             cout << "\n  Null\n";
19             break;
20         case SEVERITY_USERMSG :
21             cout << "\n  User Message\n";
22             break;
23         case SEVERITY_INCOMPLETE :
24             cout << "\n  Incomplete\n";
25             break;
26         case SEVERITY_WARNING :
27             cout << "\n  Warning\n";
28             break;
29         case SEVERITY_INPUT_ERROR :
30             cout << "\n  Input Error\n";
31             break;
32         default:
33             cout << "\n  Other\n";
34             break;
35     }
36     cout << err.DetailMsg() << "\n";
37 }
38 
39 // print istream error information for debugging purposes
IStreamState(istream & in)40 void IStreamState( istream & in ) {
41     if( in.good() ) {
42         cerr << "istream GOOD\n" << flush;
43     }
44     if( in.fail() ) {
45         cerr << "istream FAIL\n" << flush;
46     }
47     if( in.eof() ) {
48         cerr << "istream EOF\n" << flush;
49     }
50 }
51 
52 ///////////////////////////////////////////////////////////////////////////////
53 //  ReadInteger
54 // * This function reads an integer if possible
55 // * If an integer is read it is assigned to val and 1 (true) is returned.
56 // * If an integer is not read because of an error then val is left unchanged
57 //   and 0 (false) is returned.
58 // * If there is an error then the ErrorDescriptor err is set accordingly with
59 //   a severity level and error message (no error MESSAGE is set for severity
60 //   incomplete).
61 // * tokenList contains characters that terminate reading the value.
62 // * If tokenList is not zero then the istream will be read until a character
63 //   is found matching a character in tokenlist.  All values read up to the
64 //   terminating character (delimiter) must be valid or err will be set with an
65 //   appropriate error message.  A valid value may still have been assigned
66 //   but it may be followed by garbage thus an error will result.  White
67 //   space between the value and the terminating character is not considered
68 //   to be invalid.  If tokenList is null then the value must not be followed
69 //   by any characters other than white space (i.e. EOF must happen)
70 //
71 ///////////////////////////////////////////////////////////////////////////////
ReadInteger(SDAI_Integer & val,istream & in,ErrorDescriptor * err,const char * tokenList)72 int ReadInteger( SDAI_Integer & val, istream & in, ErrorDescriptor * err,
73                  const char * tokenList ) {
74     SDAI_Integer  i = 0;
75     in >> ws;
76     in >> i;
77 
78     int valAssigned = 0;
79 
80     if( !in.fail() ) {
81         valAssigned = 1;
82         val = i;
83     }
84     CheckRemainingInput( in, err, "Integer", tokenList );
85     return valAssigned;
86 }
87 
88 /// same as above but reads from a const char *
ReadInteger(SDAI_Integer & val,const char * s,ErrorDescriptor * err,const char * tokenList)89 int ReadInteger( SDAI_Integer & val, const char * s, ErrorDescriptor * err,
90                  const char * tokenList ) {
91     istringstream in( ( char * )s );
92     return ReadInteger( val, in, err, tokenList );
93 }
94 
95 ///////////////////////////////////////////////////////////////////////////////
96 // * attrValue is validated.
97 // * err is set if there is an error
98 // * If optional is 1 then a missing value will be valid otherwise severity
99 //   incomplete will be set.
100 // * If you don\'t know if the value may be optional then set it false and
101 //   check to see if SEVERITY_INCOMPLETE is set. You can change it later to
102 //   SEVERITY_NULL if it is valid for the value to be missing.  No error
103 //   'message' will be associated with the value being missing so you won\'t
104 //   have to worry about undoing an error message.
105 // * tokenList contains characters that terminate the expected value.
106 // * If tokenList is not zero then the value is expected to terminate before
107 //   a character found in tokenlist.  All values read up to the
108 //   terminating character (delimiter) must be valid or err will be set with an
109 //   appropriate error message.  White space between the value and the
110 //   terminating character is not considered to be invalid.  If tokenList is
111 //   null then attrValue must only contain a valid value and nothing else
112 //   following.
113 ///////////////////////////////////////////////////////////////////////////////
IntValidLevel(const char * attrValue,ErrorDescriptor * err,int clearError,int optional,const char * tokenList)114 Severity IntValidLevel( const char * attrValue, ErrorDescriptor * err,
115                         int clearError, int optional, const char * tokenList ) {
116     if( clearError ) {
117         err->ClearErrorMsg();
118     }
119 
120     istringstream in( ( char * )attrValue );
121     in >> ws; // skip white space
122     char c = in.peek();
123     if( in.eof() ) {
124         if( !optional ) {
125             err->GreaterSeverity( SEVERITY_INCOMPLETE );
126         }
127     } else if( c == '$' ) {
128         if( !optional ) {
129             err->GreaterSeverity( SEVERITY_INCOMPLETE );
130         }
131         in >> c;
132         CheckRemainingInput( in, err, "integer", tokenList );
133         return err->severity();
134     } else {
135         SDAI_Integer  val = 0;
136         int valAssigned = ReadInteger( val, in, err, tokenList );
137         if( !valAssigned && !optional ) {
138             err->GreaterSeverity( SEVERITY_INCOMPLETE );
139         }
140     }
141     return err->severity();
142 }
143 
WriteReal(SDAI_Real val)144 std::string WriteReal( SDAI_Real val ) {
145     char rbuf[64];
146     std::string s;
147 
148 //        out << form("%.*G", (int) Real_Num_Precision,tmp);
149     // replace the above line with this code so that writing the '.' is
150     // guaranteed for reals. If you use e or E then you get many
151     // unnecessary trailing zeros. g and G truncates all trailing zeros
152     // to save space but when no non-zero precision exists it also
153     // truncates the decimal. The decimal is required by Part 21.
154     // Also use G instead of g since G writes uppercase E (E instead of e
155     // is also required by Part 21) when scientific notation is used - DAS
156 
157     sprintf( rbuf, "%.*G", ( int ) RealNumPrecision, val );
158     if( !strchr( rbuf, '.' ) ) {
159         if( strchr( rbuf, 'E' ) || strchr( rbuf, 'e' ) ) {
160             char * expon = strchr( rbuf, 'E' );
161 
162             if( !expon ) {
163                 expon = strchr( rbuf, 'e' );
164             }
165             *expon = '\0';
166             s = rbuf;
167             s.append( "." );
168             s.append( "E" );
169             expon++;
170             s += expon;
171         } else {
172             int rindex = strlen( rbuf );
173             rbuf[rindex] = '.';
174             rbuf[rindex + 1] = '\0';
175             s = rbuf;
176         }
177     } else {
178         s = rbuf;
179     }
180     return s;
181 }
182 
WriteReal(SDAI_Real val,ostream & out)183 void WriteReal( SDAI_Real  val, ostream & out ) {
184     out << WriteReal( val );
185 }
186 
187 ///////////////////////////////////////////////////////////////////////////////
188 //  ReadReal
189 // * This function reads a real if possible
190 // * If a real is read it is assigned to val and 1 (true) is returned.
191 // * If a real is not read because of an error then val is left unchanged
192 //   and 0 (false) is returned.
193 // * If there is an error then the ErrorDescriptor err is set accordingly with
194 //   a severity level and error message (no error MESSAGE is set for severity
195 //   incomplete).
196 // * tokenList contains characters that terminate reading the value.
197 // * If tokenList is not zero then the istream will be read until a character
198 //   is found matching a character in tokenlist.  All values read up to the
199 //   terminating character (delimiter) must be valid or err will be set with an
200 //   appropriate error message.  A valid value may still have been assigned
201 //   but it may be followed by garbage thus an error will result.  White
202 //   space between the value and the terminating character is not considered
203 //   to be invalid.  If tokenList is null then the value must not be followed
204 //   by any characters other than white space (i.e. EOF must happen)
205 //
206 //   skip any leading whitespace characters
207 //   read: optional sign, at least one decimal digit, required decimal point,
208 //   zero or more decimal digits, optional letter e or E (but lower case e is
209 //   an error), optional sign, at least one decimal digit if there is an E.
210 //
211 ///////////////////////////////////////////////////////////////////////////////
ReadReal(SDAI_Real & val,istream & in,ErrorDescriptor * err,const char * tokenList)212 int ReadReal( SDAI_Real & val, istream & in, ErrorDescriptor * err,
213               const char * tokenList ) {
214     SDAI_Real  d = 0;
215 
216     // Read the real's value into a string so we can make sure it is properly
217     // formatted. e.g. a decimal point is present. If you use the stream to
218     // read the real, it won't complain if the decimal place is missing.
219     char buf[64];
220     int i = 0;
221     char c;
222     ErrorDescriptor e;
223 
224     in >> ws; // skip white space
225 
226     // read optional sign
227     c = in.peek();
228     if( c == '+' || c == '-' ) {
229         in.get( buf[i++] );
230         c = in.peek();
231     }
232 
233     // check for required initial decimal digit
234     if( !isdigit( c ) ) {
235         e.severity( SEVERITY_WARNING );
236         e.DetailMsg( "Real must have an initial digit.\n" );
237     }
238     // read one or more decimal digits
239     while( isdigit( c ) ) {
240         in.get( buf[i++] );
241         c = in.peek();
242     }
243 
244     // read Part 21 required decimal point
245     if( c == '.' ) {
246         in.get( buf[i++] );
247         c = in.peek();
248     } else {
249         // It may be the number they wanted but it is incompletely specified
250         // without a decimal and thus it is an error
251         e.GreaterSeverity( SEVERITY_WARNING );
252         e.AppendToDetailMsg( "Reals are required to have a decimal point.\n" );
253     }
254 
255     // read optional decimal digits
256     while( isdigit( c ) ) {
257         in.get( buf[i++] );
258         c = in.peek();
259     }
260 
261     // try to read an optional E for scientific notation
262     if( ( c == 'e' ) || ( c == 'E' ) ) {
263         if( c == 'e' ) {
264             // this is incorrectly specified and thus is an error
265             e.GreaterSeverity( SEVERITY_WARNING );
266             e.AppendToDetailMsg(
267                 "Reals using scientific notation must use upper case E.\n" );
268         }
269         in.get( buf[i++] ); // read the E
270         c = in.peek();
271 
272         // read optional sign
273         if( c == '+' || c == '-' ) {
274             in.get( buf[i++] );
275             c = in.peek();
276         }
277 
278         // read required decimal digit (since it has an E)
279         if( !isdigit( c ) ) {
280             e.GreaterSeverity( SEVERITY_WARNING );
281             e.AppendToDetailMsg(
282                 "Real must have at least one digit following E for scientific notation.\n" );
283         }
284         // read one or more decimal digits
285         while( isdigit( c ) ) {
286             in.get( buf[i++] );
287             c = in.peek();
288         }
289     }
290     buf[i] = '\0';
291 
292     istringstream in2( ( char * )buf );
293 
294     // now that we have the real the stream will be able to salvage reading
295     // whatever kind of format was used to represent the real.
296     in2 >> d;
297 
298     int valAssigned = 0;
299 
300     if( !in2.fail() ) {
301         valAssigned = 1;
302         val = d;
303         err->GreaterSeverity( e.severity() );
304         err->AppendToDetailMsg( e.DetailMsg() );
305     } else {
306         val = S_REAL_NULL;
307     }
308 
309     CheckRemainingInput( in, err, "Real", tokenList );
310     return valAssigned;
311 }
312 
313 /// same as above but reads from a const char *
ReadReal(SDAI_Real & val,const char * s,ErrorDescriptor * err,const char * tokenList)314 int ReadReal( SDAI_Real & val, const char * s, ErrorDescriptor * err,
315               const char * tokenList ) {
316     istringstream in( ( char * )s );
317     return ReadReal( val, in, err, tokenList );
318 }
319 
320 ///////////////////////////////////////////////////////////////////////////////
321 // * attrValue is validated.
322 // * err is set if there is an error
323 // * If optional is 1 then a missing value will be valid otherwise severity
324 //   incomplete will be set.
325 // * If you don\'t know if the value may be optional then set it false and
326 //   check to see if SEVERITY_INCOMPLETE is set. You can change it later to
327 //   SEVERITY_NULL if it is valid for the value to be missing.  No error
328 //   'message' will be associated with the value being missing so you won\'t
329 //   have to worry about undoing an error message.
330 // * tokenList contains characters that terminate the expected value.
331 // * If tokenList is not zero then the value is expected to terminate before
332 //   a character found in tokenlist.  All values read up to the
333 //   terminating character (delimiter) must be valid or err will be set with an
334 //   appropriate error message.  White space between the value and the
335 //   terminating character is not considered to be invalid.  If tokenList is
336 //   null then attrValue must only contain a valid value and nothing else
337 //   following.
338 ///////////////////////////////////////////////////////////////////////////////
RealValidLevel(const char * attrValue,ErrorDescriptor * err,int clearError,int optional,const char * tokenList)339 Severity RealValidLevel( const char * attrValue, ErrorDescriptor * err,
340                          int clearError, int optional, const char * tokenList ) {
341     if( clearError ) {
342         err->ClearErrorMsg();
343     }
344 
345     istringstream in( ( char * )attrValue );
346     in >> ws; // skip white space
347     char c = in.peek();
348     if( in.eof() ) {
349         if( !optional ) {
350             err->GreaterSeverity( SEVERITY_INCOMPLETE );
351         }
352     } else if( c == '$' ) {
353         if( !optional ) {
354             err->GreaterSeverity( SEVERITY_INCOMPLETE );
355         }
356         in >> c;
357         CheckRemainingInput( in, err, "real", tokenList );
358         return err->severity();
359     } else {
360         SDAI_Real  val = 0;
361         int valAssigned = ReadReal( val, in, err, tokenList );
362         if( !valAssigned && !optional ) {
363             err->GreaterSeverity( SEVERITY_INCOMPLETE );
364         }
365     }
366     return err->severity();
367 }
368 
369 /**
370  *  ReadNumber - read as a real number
371  * * This function reads a number if possible
372  * * If a number is read it is assigned to val and 1 (true) is returned.
373  * * If a number is not read because of an error then val is left unchanged
374  *   and 0 (false) is returned.
375  * * If there is an error then the ErrorDescriptor err is set accordingly with
376  *   a severity level and error message (no error MESSAGE is set for severity
377  *   incomplete).
378  * * tokenList contains characters that terminate reading the value.
379  * * If tokenList is not zero then the istream will be read until a character
380  *   is found matching a character in tokenlist.  All values read up to the
381  *   terminating character (delimiter) must be valid or err will be set with an
382  *   appropriate error message.  A valid value may still have been assigned
383  *   but it may be followed by garbage thus an error will result.  White
384  *   space between the value and the terminating character is not considered
385  *   to be invalid.  If tokenList is null then the value must not be followed
386  *   by any characters other than white space (i.e. EOF must happen)
387  */
ReadNumber(SDAI_Real & val,istream & in,ErrorDescriptor * err,const char * tokenList)388 int ReadNumber( SDAI_Real & val, istream & in, ErrorDescriptor * err,
389                 const char * tokenList ) {
390     SDAI_Real  d = 0;
391     in >> ws;
392     in >> d;
393 
394     int valAssigned = 0;
395     if( !in.fail() ) {
396         valAssigned = 1;
397         val = d;
398     }
399     CheckRemainingInput( in, err, "Number", tokenList );
400     return valAssigned;
401 }
402 
403 /// same as above but reads from a const char *
ReadNumber(SDAI_Real & val,const char * s,ErrorDescriptor * err,const char * tokenList)404 int ReadNumber( SDAI_Real & val, const char * s, ErrorDescriptor * err,
405                 const char * tokenList ) {
406     istringstream in( ( char * )s );
407     return ReadNumber( val, in, err, tokenList );
408 }
409 
410 
411 ///////////////////////////////////////////////////////////////////////////////
412 // * attrValue is validated.
413 // * err is set if there is an error
414 // * If optional is 1 then a missing value will be valid otherwise severity
415 //   incomplete will be set.
416 // * If you don\'t know if the value may be optional then set it false and
417 //   check to see if SEVERITY_INCOMPLETE is set. You can change it later to
418 //   SEVERITY_NULL if it is valid for the value to be missing.  No error
419 //   'message' will be associated with the value being missing so you won\'t
420 //   have to worry about undoing an error message.
421 // * tokenList contains characters that terminate the expected value.
422 // * If tokenList is not zero then the value is expected to terminate before
423 //   a character found in tokenlist.  All values read up to the
424 //   terminating character (delimiter) must be valid or err will be set with an
425 //   appropriate error message.  White space between the value and the
426 //   terminating character is not considered to be invalid.  If tokenList is
427 //   null then attrValue must only contain a valid value and nothing else
428 //   following.
429 ///////////////////////////////////////////////////////////////////////////////
NumberValidLevel(const char * attrValue,ErrorDescriptor * err,int clearError,int optional,const char * tokenList)430 Severity NumberValidLevel( const char * attrValue, ErrorDescriptor * err,
431                            int clearError, int optional, const char * tokenList ) {
432     if( clearError ) {
433         err->ClearErrorMsg();
434     }
435 
436     istringstream in( ( char * )attrValue );
437     in >> ws; // skip white space
438     char c = in.peek();
439     if( in.eof() ) {
440         if( !optional ) {
441             err->GreaterSeverity( SEVERITY_INCOMPLETE );
442         }
443     } else if( c == '$' ) {
444         if( !optional ) {
445             err->GreaterSeverity( SEVERITY_INCOMPLETE );
446         }
447         in >> c;
448         CheckRemainingInput( in, err, "number", tokenList );
449         return err->severity();
450     } else {
451         SDAI_Real  val = 0;
452         int valAssigned = ReadNumber( val, in, err, tokenList );
453         if( !valAssigned && !optional ) {
454             err->GreaterSeverity( SEVERITY_INCOMPLETE );
455         }
456     }
457     return err->severity();
458 }
459 
460 /// assign 's' so that it contains an exchange file format string read from 'in'.
PushPastString(istream & in,std::string & s,ErrorDescriptor * err)461 void PushPastString( istream & in, std::string & s, ErrorDescriptor * err ) {
462     s += GetLiteralStr( in, err );
463 }
464 
465 /**
466  * assign 's' so that it contains an exchange file format aggregate read from 'in'.
467  * This is used to read aggregates that are part of multidimensional aggregates.
468  */
PushPastImbedAggr(istream & in,std::string & s,ErrorDescriptor * err)469 void PushPastImbedAggr( istream & in, std::string & s, ErrorDescriptor * err ) {
470     char messageBuf[BUFSIZ];
471     messageBuf[0] = '\0';
472 
473     char c;
474     in >> ws;
475     in.get( c );
476 
477     if( c == '(' ) {
478         s += c;
479         in.get( c );
480         while( in.good() && ( c != ')' ) ) {
481             if( c == '(' ) {
482                 in.putback( c );
483                 PushPastImbedAggr( in, s, err );
484             } else if( c == STRING_DELIM ) {
485                 in.putback( c );
486                 PushPastString( in, s, err );
487             } else {
488                 s += c;
489             }
490             in.get( c );
491         }
492         if( c != ')' ) {
493             err->GreaterSeverity( SEVERITY_INPUT_ERROR );
494             sprintf( messageBuf, "Invalid aggregate value.\n" );
495             err->AppendToDetailMsg( messageBuf );
496             s.append( ")" );
497         } else {
498             s += c;
499         }
500     }
501 }
502 
503 /**
504  * assign 's' so that it contains an exchange file format aggregate read from 'in'.
505  * This is used to read a single dimensional aggregate (i.e. it is not allowed
506  * to contain an aggregate as an element.
507  */
PushPastAggr1Dim(istream & in,std::string & s,ErrorDescriptor * err)508 void PushPastAggr1Dim( istream & in, std::string & s, ErrorDescriptor * err ) {
509     char messageBuf[BUFSIZ];
510     messageBuf[0] = '\0';
511 
512     char c;
513     in >> ws;
514     in.get( c );
515 
516     if( c == '(' ) {
517         s += c;
518         in.get( c );
519         while( in.good() && ( c != ')' ) ) {
520             if( c == '(' ) {
521                 err->GreaterSeverity( SEVERITY_WARNING );
522                 sprintf( messageBuf, "Invalid aggregate value.\n" );
523                 err->AppendToDetailMsg( messageBuf );
524             }
525 
526             if( c == STRING_DELIM ) {
527                 in.putback( c );
528                 PushPastString( in, s, err );
529             } else {
530                 s += c;
531             }
532             in.get( c );
533         }
534         if( c != ')' ) {
535             err->GreaterSeverity( SEVERITY_INPUT_ERROR );
536             sprintf( messageBuf, "Invalid aggregate value.\n" );
537             err->AppendToDetailMsg( messageBuf );
538             s.append( ")" );
539         } else {
540             s += c;
541         }
542     }
543 }
544 
545 /**
546 * FindStartOfInstance reads to the beginning of an instance marked by #
547 * it copies what is read to the std::string inst.  It leaves the # on the
548 * istream.
549 */
FindStartOfInstance(istream & in,std::string & inst)550 Severity FindStartOfInstance( istream & in, std::string & inst ) {
551     char c = 0;
552     ErrorDescriptor errs;
553     SDAI_String  tmp;
554 
555     while( in.good() ) {
556         in >> c;
557         switch( c )  {
558             case '#':  //  found char looking for.
559                 in.putback( c );
560                 return SEVERITY_NULL;
561 
562             case '\'':  // get past the string
563                 in.putback( c );
564                 tmp.STEPread( in, &errs );
565                 inst.append( tmp.c_str() );
566                 break;
567 
568             case '\0':  // problem in input ?
569                 return SEVERITY_INPUT_ERROR;
570 
571             default:
572                 inst += c;
573         }
574     }
575     return SEVERITY_INPUT_ERROR;
576 }
577 
578 /**
579 * SkipInstance reads in an instance terminated with ;.  it copies
580 * what is read to the std::string inst.
581 */
SkipInstance(istream & in,std::string & inst)582 Severity SkipInstance( istream & in, std::string & inst ) {
583     char c = 0;
584     ErrorDescriptor errs;
585     SDAI_String  tmp;
586 
587     while( in.good() ) {
588         in >> c;
589         switch( c )  {
590             case ';':  //  end of instance reached
591                 return SEVERITY_NULL;
592 
593             case '\'':  // get past the string
594                 in.putback( c );
595                 tmp.STEPread( in, &errs );
596                 inst.append( tmp.c_str() );
597                 break;
598 
599             case '\0':  // problem in input ?
600                 return SEVERITY_INPUT_ERROR;
601 
602             default:
603                 inst += c;
604         }
605     }
606     return SEVERITY_INPUT_ERROR;
607 }
608 
609 
610 /**
611 // This reads a simple record.  It is used when parsing externally mapped
612 // entities to skip to the next entity type name part of the externally mapped
613 // record.  It does not expect a semicolon at the end since the pieces in an
614 // external mapping don't have them.  If you are reading a simple record in the
615 // form of an internal mapping you will have to read the semicolon.
616 */
SkipSimpleRecord(istream & in,std::string & buf,ErrorDescriptor * err)617 const char * SkipSimpleRecord( istream & in, std::string & buf, ErrorDescriptor * err ) {
618     char c;
619     std::string s;
620 
621     in >> ws;
622     in.get( c );
623     if( c == '(' ) { // beginning of record
624         buf += c;
625         while( in.get( c ) && ( c != ')' ) && ( err->severity() > SEVERITY_INPUT_ERROR ) ) {
626             if( c == '\'' ) {
627                 in.putback( c );
628                 s.clear();
629                 PushPastString( in, s, err );
630                 buf.append( s.c_str() );
631             } else if( c == '(' ) {
632                 in.putback( c );
633                 s.clear();
634                 PushPastImbedAggr( in, s, err );
635                 buf.append( s.c_str() );
636             } else {
637                 buf += c;
638             }
639         }
640         if( !in.good() ) {
641             err->GreaterSeverity( SEVERITY_INPUT_ERROR );
642             err->DetailMsg( "File problems reading simple record.\n" );
643         }
644         buf.append( ")" );
645     } else {
646         in.putback( c );    // put back open paren
647     }
648     return const_cast<char *>( buf.c_str() );
649 }
650 
651 /**
652 // This reads a part 21 definition of keyword.  This includes the name of
653 // entity types.  To read a user-defined keyword: read the '!' then call
654 // this function with skipInitWS turned off.
655 **/
ReadStdKeyword(istream & in,std::string & buf,int skipInitWS)656 const char * ReadStdKeyword( istream & in, std::string & buf, int skipInitWS ) {
657     char c;
658     if( skipInitWS ) {
659         in >> ws;
660     }
661 
662     while( in.get( c ) && !isspace( c ) && ( isalnum( c ) || ( c == '_' ) ) ) {
663         buf += c;
664     }
665 
666     if( in.eof() || in.good() ) {
667         in.putback( c );
668     }
669 
670     return const_cast<char *>( buf.c_str() );
671 }
672 
673 /***************************
674 This function returns a null terminated const char* for the
675 characters read from the istream up to, but not including
676 the first character found in the set of delimiters, or the
677 whitespace character. It leaves the delimiter on the istream.
678 
679 The string is returned in a static buffer, so it will change
680 the next time the function is called.
681 
682 Keywords are special strings of characters indicating the instance
683 of an entity of a specific type. They shall consist of uppercase letters,
684 digits, underscore characters, and possibly an exclamation mark.
685 The "!" shall appear only once, and only as the first character.
686 ***************************/
GetKeyword(istream & in,const char * delims,ErrorDescriptor & err)687 const char * GetKeyword( istream & in, const char * delims, ErrorDescriptor & err ) {
688     char c;
689     int sz = 1;
690     static std::string str;
691 
692     str = "";
693     in.get( c );
694     while( !( ( isspace( c ) ) || ( strchr( delims, c ) ) ) ) {
695         //check to see if the char is valid
696         if( !( ( isupper( c ) ) ||
697                 ( isdigit( c ) ) ||
698                 ( c == '_' )   ||
699                 ( c == '-' )   ||  //for reading 'ISO-10303-21'
700                 ( ( c == '!' ) && ( sz == 1 ) ) ) ) {
701             cerr << "Error: Invalid character \'" << c <<
702                  "\' in GetKeyword.\nkeyword was: " << str << "\n";
703             err.GreaterSeverity( SEVERITY_WARNING );
704             in.putback( c );
705             return const_cast<char *>( str.c_str() );
706         }
707         if( !in.good() ) {
708             break;    //BUG: should do something on eof()
709         }
710         str += c;
711         ++sz;
712         in.get( c );
713     }
714     in.putback( c );
715     return const_cast<char *>( str.c_str() );
716 }
717 
718 /**
719  * return 1 if found the keyword 'ENDSEC' with optional space and a ';'
720  * otherwise return 0. This gobbles up the input stream until it knows
721  * that it does or does not have the ENDSEC keyword (and semicolon). i.e.
722  * the first character that stops matching the keyword ENDSEC; (including the
723  * semicolon) will be put back onto the istream, everything else will remain
724  * read.  It is this way so that checking for the keywd the next time will
725  * start with the correct char or if it doesn't find it the non-matching char
726  * may be what you are looking for next (e.g. someone typed in END; and the
727  * next chars are DATA; for the beginning of the data section).
728  * FIXME putback() doesn't work well on all platforms
729  */
FoundEndSecKywd(istream & in)730 int FoundEndSecKywd( istream & in ) {
731     char c;
732     in >> ws;
733     in.get( c );
734 
735     if( c == 'E' ) {
736         in.get( c );
737         if( c == 'N' ) {
738             in.get( c );
739             if( c == 'D' ) {
740                 in.get( c );
741                 if( c == 'S' ) {
742                     in.get( c );
743                     if( c == 'E' ) {
744                         in.get( c );
745                         if( c == 'C' ) {
746                             in >> ws;
747                             in.get( c );
748                             if( c == ';' ) {
749                                 return 1;
750                             } else {
751                                 in.putback( c );
752                             }
753                         } else {
754                             in.putback( c );
755                         }
756                     } else {
757                         in.putback( c );
758                     }
759                 } else {
760                     in.putback( c );
761                 }
762             } else {
763                 in.putback( c );
764             }
765         } else {
766             in.putback( c );
767         }
768     } else {
769         in.putback( c );
770     }
771     // error
772     return 0;
773 }
774 
775 // Skip over everything in s until the start of a comment. If it is a well
776 // formatted comment append the comment (including delimiters) to
777 // std::string ss.  Return a pointer in s just past what was read as a comment.
778 // If no well formed comment was found, a pointer to the null char in s is
779 // returned.  If one is found ss is appended with it and a pointer just
780 // past the comment in s is returned. Note* a carraige return ('\n') is added
781 // after the comment that is appended.
ReadComment(std::string & ss,const char * s)782 const char * ReadComment( std::string & ss, const char * s ) {
783     std::string ssTmp;
784 
785     if( s ) {
786         int endComment = 0;
787         while( *s && *s != '/' ) {
788             s++;    // skip leading everything
789         }
790         if( *s == '/' ) {
791             s++;
792             if( *s == '*' ) { // found a comment
793                 ssTmp.append( "/*" );
794                 s++;
795                 while( *s && !endComment ) {
796                     if( *s == '*' ) {
797                         ssTmp += *s;
798                         s++;
799                         if( *s == '/' ) {
800                             endComment = 1;
801                             ssTmp += *s;
802                             ssTmp.append( "\n" );
803                         } else {
804                             s--;
805                         }
806                     } else {
807                         ssTmp += *s;
808                     }
809                     s++;
810                 }
811             }
812         }
813         if( endComment ) {
814             ss.append( ssTmp.c_str() );
815         }
816     }
817     return s;
818 }
819 
820 /***************************
821  * Reads a comment. If there is a comment it is returned as
822  * char * in space provided in std::string s.
823  * If there is not a comment returns null pointer.
824  * After skipping white space it expects a slash followed by
825  * an asterisk (which I didn't type to avoid messing up the
826  * compiler).  It ends with asterisk followed by slash.
827  * If first char from 'in' is a slash it will remain read
828  * whether or not there was a comment.  If there is no comment
829  * only the slash will be read from 'in'.
830  * FIXME putback() doesn't work well on all platforms
831 ***************************/
ReadComment(istream & in,std::string & s)832 const char * ReadComment( istream & in, std::string & s ) {
833     char c = '\0';
834     in >> ws;
835     in >> c;
836 
837     // it looks like a comment so far
838     if( c == '/' ) { // leave slash read from stream
839         in.get( c ); // won't skip space
840         if( c == '*' ) { // it is a comment
841             in >> ws; // skip leading comment space
842             int commentLength = 0;
843 
844             // only to keep it from completely gobbling up input
845             while( commentLength <= MAX_COMMENT_LENGTH ) {
846                 in.get( c );
847                 if( c == '*' ) { // looks like start of end comment
848                     in.get( c );
849                     if( c == '/' ) { // it is end of comment
850                         return s.c_str();    // return comment as a string
851                     } else { // it is not end of comment
852                         // so store the * and put back the other char
853                         s.append( "*" );
854                         in.putback( c );
855                         commentLength++;
856                     }
857                 } else {
858                     s += c;
859                     commentLength++;
860                 }
861             } // end while
862             cout << "ERROR comment longer than maximum comment length of "
863                  << MAX_COMMENT_LENGTH << "\n"
864                  << "Will try to recover...\n";
865             std::string tmp;
866             SkipInstance( in, tmp );
867             return s.c_str();
868         }
869         // leave slash read from stream... assume caller already knew there was
870         //  a slash, leave it off stream so they don't think this funct needs
871         // to be called again
872         else { // not a comment
873             in.putback( c );    // put non asterisk char back on input stream
874         }
875     } else { // first non-white char is not a slash
876         in.putback( c );    // put non slash char back on input stream
877     }
878 
879     return 0; // no comment string to return
880 }
881 
882 /***************************
883  ** Read a Print Control Directive from the istream
884  ** "\F\" == formfeed
885  ** "\N\" == newline
886  ***************************/
ReadPcd(istream & in)887 Severity ReadPcd( istream & in ) {
888     char c;
889     in.get( c );
890     if( c == '\\' ) {
891         in.get( c );
892         if( c == 'F' || c == 'N' ) {
893             in.get( c );
894             if( c == '\\' ) {
895                 in.get( c );
896                 return SEVERITY_NULL;
897             }
898         }
899     }
900     cerr << "Print control directive expected.\n";
901     return SEVERITY_WARNING;
902 }
903 
904 
905 /******************************
906 This function reads through token separators
907 from an istream. It returns when a token
908 separator is not the next thing on the istream.
909 The token separators are blanks, explicit print control directives,
910 and comments.
911 Part 21 considers the blank to be the space character,
912 but this function considers blanks to be the return value of isspace(c)
913 ******************************/
ReadTokenSeparator(istream & in,std::string * comments)914 void ReadTokenSeparator( istream & in, std::string * comments ) {
915     char c;
916     std::string s; // used if need to read a comment
917 
918     if( in.eof() ) {
919         //BUG: no error message is reported
920         return;
921     }
922 
923     while( in ) {
924         in >> ws; // skip white space.
925         c = in.peek(); // look at next char on input stream
926 
927         switch( c ) {
928             case '/': // read p21 file comment
929                 s.clear();
930                 ReadComment( in, s );
931                 if( !s.empty() && comments ) {
932                     comments->append( "/*" );
933                     comments->append( s.c_str() );
934                     comments->append( "*/\n" );
935                 }
936                 break;
937 
938             case '\\': // try to read a print control directive
939                 ReadPcd( in );
940                 break;
941             case '\n':
942                 in.ignore();
943                 break;
944             default:
945                 return;
946         }
947     }
948 }
949