0029151: GCC 7.1 warnings "this statement may fall through" [-Wimplicit-fallthrough=]
[occt.git] / src / LDOM / LDOM_XmlReader.cxx
1 // Created on: 2001-07-20
2 // Created by: Alexander GRIGORIEV
3 // Copyright (c) 2001-2014 OPEN CASCADE SAS
4 //
5 // This file is part of Open CASCADE Technology software library.
6 //
7 // This library is free software; you can redistribute it and/or modify it under
8 // the terms of the GNU Lesser General Public License version 2.1 as published
9 // by the Free Software Foundation, with special exception defined in the file
10 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11 // distribution for complete text of the license and disclaimer of any warranty.
12 //
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
15
16 //AGV 060302: Input from istream
17 //            AGV 130302: bug corr: was error if strlen(root_elem_name) < 7
18
19 #include <LDOM_XmlReader.hxx>
20 #include <Standard_Stream.hxx>
21 #include <LDOM_MemManager.hxx>
22 #include <LDOM_BasicAttribute.hxx>
23 #include <LDOM_CharReference.hxx>
24 #include <LDOM_OSStream.hxx>
25
26 #include <string.h>
27 #include <errno.h>
28 #ifdef _MSC_VER
29 #include <io.h>
30 #else
31 #include <unistd.h>
32 #endif
33
34 //#include <ctype.h>
35
36 const int XML_MIN_BUFFER = 10;
37
38 typedef enum {
39   STATE_WAITING = 0,
40   STATE_HEADER,
41   STATE_DOCTYPE,
42   STATE_DOCTYPE_MARKUP,
43   STATE_ELEMENT,
44   STATE_ELEMENT_END,
45   STATE_ATTRIBUTE_NAME,
46   STATE_ATTRIBUTE_EQUAL,
47   STATE_ATTRIBUTE_VALUE,
48   STATE_COMMENT,
49   STATE_CDATA,
50   STATE_TEXT
51 } ParserState;
52
53 #define TEXT_COMPARE(aPtr,aPattern) \
54   (memcmp ((aPtr), (aPattern), sizeof(aPattern) - 1) == 0)
55
56 static Standard_Boolean isName          (const char             * aString,
57                                          const char             * aStringEnd,
58                                          const char             *& aNameEnd);
59
60 //=======================================================================
61 //function : LDOM_XmlReader()
62 //purpose  : Constructor (file descriptor)
63 //=======================================================================
64
65 LDOM_XmlReader::LDOM_XmlReader (
66                                 const Handle(LDOM_MemManager)&  theDocument,
67                                 TCollection_AsciiString&        theErrorString,
68                                 const Standard_Boolean theTagPerStep)
69 : myEOF      (Standard_False),
70   myError    (theErrorString),
71   myDocument (theDocument),
72   myElement  (NULL),
73   myLastChild(NULL), 
74   myPtr      (&myBuffer[0]),
75   myEndPtr   (&myBuffer[0]),
76   myTagPerStep (theTagPerStep)
77 {
78 }
79
80 //=======================================================================
81 //function : ReadRecord
82 //purpose  : Read a record from XML file
83 //=======================================================================
84
85 LDOM_XmlReader::RecordType LDOM_XmlReader::ReadRecord (Standard_IStream& theIStream,
86                                         LDOM_OSStream& theData)
87 {
88   theData.Clear();
89   myError.Clear();
90   ParserState aState = STATE_WAITING;
91   const char * aStartData = NULL, * aNameEnd = NULL, * aPtr;
92   LDOMBasicString anAttrName, anAttrValue;
93   char anAttDelimiter = '\0';
94   Standard_Boolean aHasRead = Standard_False;
95
96   for(;;) {
97     //  Check if the current file buffer is exhausted
98     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
99     //  There should always be some bytes available in the buffer for analysis
100     Standard_Integer aBytesRest = (Standard_Integer)(myEndPtr - myPtr);
101     if (aBytesRest < XML_MIN_BUFFER)
102     {
103       if (myEOF == Standard_True)
104       {
105         if (aBytesRest <= 0)
106           break;                        // END of processing
107       }
108       else if (myTagPerStep && aHasRead)
109       {
110         // in myTagPerStep mode, we should parse the buffer to the end before
111         // getting more characters from the stream.
112       }
113       else
114       {
115       // If we are reading some data, save the beginning and preserve the state
116         if (aStartData /* && aState != STATE_WAITING */) {
117           if (myPtr > aStartData)
118             theData.rdbuf()->sputn(aStartData, myPtr - aStartData);
119           aStartData = &myBuffer[0];
120         }
121       // Copy the rest of file data to the beginning of buffer
122         if (aBytesRest > 0)
123           memcpy (&myBuffer[0], myPtr, aBytesRest);
124
125       // Read the full buffer and reset start and end buffer pointers
126         myPtr    = &myBuffer[0];
127         Standard_Size aNBytes;
128
129         if (myTagPerStep)
130         {
131           theIStream.getline (&myBuffer[aBytesRest], XML_BUFFER_SIZE - aBytesRest, '>');
132           aHasRead = Standard_True;
133         }
134         else
135         {
136           theIStream.read (&myBuffer[aBytesRest], XML_BUFFER_SIZE - aBytesRest);
137         }
138         aNBytes = (Standard_Size)theIStream.gcount();
139         
140         if (aNBytes == 0)
141         {
142           myEOF = Standard_True;                  // END-OF-FILE
143         }
144         else if (myTagPerStep)
145         {
146           // replace \0 (being inserted by getline method) with > 
147           myBuffer[aBytesRest + aNBytes - 1] = '>';
148         }
149         myEndPtr = &myBuffer[aBytesRest + aNBytes];
150         myBuffer[aBytesRest + aNBytes] = '\0';
151       }
152     }
153
154     //  Check the character data
155     switch (aState) {
156
157       // Checking the characters in STATE_WAITING (blank, TEXT or markup)
158       // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
159     case STATE_WAITING:
160       switch (myPtr[0]) {
161       case ' ':
162       case '\t':
163       case '\n':
164       case '\r':
165         ++ myPtr;
166         continue;
167       case '<':
168         // XML markup found, then make detect the record type
169         switch (myPtr[1]) {
170         case '?':
171           aState = STATE_HEADER;
172           myPtr += 2;
173           aStartData = myPtr;
174           continue;
175         case '/':
176           aState = STATE_ELEMENT_END;
177           myPtr += 2;
178           aStartData = myPtr;
179           continue;
180         case '!':
181           if (myPtr[2] == '-' && myPtr[3] == '-') {
182             aState = STATE_COMMENT;
183             myPtr += 4;
184           } else if (TEXT_COMPARE (&myPtr[2], "DOCTYPE")) {
185             char ch = myPtr[9];
186             if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r')
187               break;
188             aState = STATE_DOCTYPE;
189             myPtr += 10;
190           } else if (TEXT_COMPARE (&myPtr[2], "[CDATA[")) {
191             aState = STATE_CDATA;
192             myPtr += 9;
193           } else break;                   // ERROR
194           aStartData = myPtr;
195           continue;
196         default:
197           if (::isName (&myPtr[1], myEndPtr, aNameEnd)) {
198             aStartData = myPtr + 1;
199             myPtr = aNameEnd;
200             if (myPtr < myEndPtr) {
201               myElement = & LDOM_BasicElement::Create (aStartData,
202                                                        (Standard_Integer)(myPtr - aStartData),
203                                                        myDocument);
204               myLastChild = NULL;
205               aState = STATE_ATTRIBUTE_NAME;
206               aStartData = NULL;
207             }else
208               aState = STATE_ELEMENT;
209             continue;
210           }       // otherwise ERROR
211         }     // end of switch
212         myError = "Unknown XML object: ";
213         myError += TCollection_AsciiString ((const Standard_CString)myPtr,
214                                             XML_MIN_BUFFER);
215         return XML_UNKNOWN;
216       case '\0':
217         if (myEOF == Standard_True) continue;
218         Standard_FALLTHROUGH
219       default:
220         //      Limitation: we do not treat '&' as special character
221         aPtr = (const char *) memchr (myPtr, '<', myEndPtr - myPtr);
222         if (aPtr) {
223           // The end of text field reached
224           theData.rdbuf()->sputn(myPtr, aPtr - myPtr);
225           myPtr = aPtr;
226           return XML_TEXT;
227         }
228         aState = STATE_TEXT;
229         aStartData = myPtr;
230         myPtr = myEndPtr;
231         aHasRead = Standard_False;
232       }   // end of checking in STATE_WAITING
233       continue;
234
235       // Checking the characters in STATE_HEADER, seek for "?>" sequence
236       // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
237     case STATE_HEADER:
238       aPtr = (const char *) memchr (aStartData, '?', (myEndPtr-1) - aStartData);
239       if (aPtr) {
240         // The end of XML declaration found
241         if (aPtr[1] != '>') {           // ERROR
242           myError = "Character \'>\' is expected in the end of XML declaration";
243           return XML_UNKNOWN;
244         }
245         // The XML declaration is retrieved
246         theData.rdbuf()->sputn(aStartData, aPtr - aStartData);
247         myPtr = aPtr + 2;
248         return XML_HEADER;
249       }
250       myPtr = myEndPtr - 1;
251       aHasRead = Standard_False;
252       continue;
253
254       // Checking the characters in STATE_DOCTYPE, seek for "]>" sequence
255       // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
256     case STATE_DOCTYPE:
257       for (aPtr = aStartData; aPtr < myEndPtr-1; aPtr++) {
258         const int aChar = aPtr[0];
259         if (aChar == '[') {
260           aState = STATE_DOCTYPE_MARKUP;
261           aStartData = &aPtr[1];
262           goto state_doctype_markup;
263         }
264         if (aChar == '>') {
265           // The DOCTYPE declaration is retrieved
266           theData.rdbuf()->sputn(aStartData, aPtr - aStartData - 1);
267           myPtr = aPtr + 1;
268           return XML_DOCTYPE;
269         }
270       }
271       myPtr = myEndPtr - 1;
272       aHasRead = Standard_False;
273       continue;
274
275     state_doctype_markup:
276     case STATE_DOCTYPE_MARKUP:
277       aPtr = (const char *) memchr (aStartData, ']', (myEndPtr-1) - aStartData);
278       if (aPtr) {
279         // The end of DOCTYPE declaration found
280         if (aPtr[1] != '>') {           // ERROR
281           myError =
282             "Character \'>\' is expected in the end of DOCTYPE declaration";
283           return XML_UNKNOWN;
284         }
285         // The DOCTYPE declaration is retrieved
286         theData.rdbuf()->sputn(aStartData, aPtr - aStartData);
287         myPtr = aPtr + 2;
288         return XML_DOCTYPE;
289       }
290       myPtr = myEndPtr - 1;
291       aHasRead = Standard_False;
292       continue;
293
294         // Checking the characters in STATE_COMMENT, seek for "-->" sequence
295         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
296     case STATE_COMMENT:
297       aPtr = aStartData;
298       for(;;) {
299         aPtr = (const char *) memchr (aPtr, '-', (myEndPtr - 2) - aPtr);
300         if (aPtr == NULL) break;
301         if (aPtr[1] != '-') ++ aPtr;
302         else {
303           if (aPtr[2] != '>') {       // ERROR
304             myError = "Character \'>\' is expected in the end of comment";
305             return XML_UNKNOWN;
306           }
307           theData.rdbuf()->sputn(aStartData, aPtr - aStartData);
308           myPtr = aPtr + 3;
309           return XML_COMMENT;
310         }
311       }
312       myPtr = myEndPtr - 2;
313       aHasRead = Standard_False;
314       continue;
315
316         // Checking the characters in STATE_TEXT, seek for "<"
317         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
318     case STATE_TEXT:
319       aPtr = (const char *) memchr (aStartData, '<', myEndPtr - aStartData);
320       if (aPtr) {
321         // The end of text field reached
322         theData.rdbuf()->sputn(aStartData, aPtr - aStartData);
323         myPtr = aPtr;
324         return XML_TEXT;
325       }
326       myPtr = myEndPtr;
327       aHasRead = Standard_False;
328       continue;
329
330         // Checking the characters in STATE_CDATA, seek for "]]"
331         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
332     case STATE_CDATA:
333       aPtr = aStartData;
334       for(;;) {
335         aPtr = (const char *) memchr (aPtr, ']', (myEndPtr - 1) - aStartData);
336         if (aPtr == NULL) break;
337         if (aPtr[1] != ']') {           // ERROR
338           myError = "Characters \']]\' are expected in the end of CDATA";
339           return XML_UNKNOWN;
340         }
341         theData.rdbuf()->sputn(aStartData, aPtr - aStartData);
342         myPtr = aPtr + 2;
343         return XML_CDATA;
344       }
345       myPtr = myEndPtr - 1;
346       aHasRead = Standard_False;
347       continue;
348
349         // Checking the characters in STATE_ELEMENT, seek the end of TagName
350         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
351     case STATE_ELEMENT:
352       if (::isName (myPtr, myEndPtr, aNameEnd) == Standard_False)
353         if (theData.Length() == 0 || aNameEnd != myPtr) {
354           myError = "Invalid tag name";
355           return XML_UNKNOWN;
356         }
357       {
358         theData.rdbuf()->sputn(aStartData, aNameEnd - aStartData);
359         char* aDataString = (char *)theData.str();
360         myElement = & LDOM_BasicElement::Create (aDataString, theData.Length(),
361                                                  myDocument);
362         theData.Clear();
363         myLastChild = NULL;
364         delete [] aDataString;
365         aState = STATE_ATTRIBUTE_NAME;
366         aStartData = NULL;
367         myPtr = aNameEnd;
368         continue;
369       }
370         // Parsing a single attribute (STATE_ATTRIBUTE)
371         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
372     case STATE_ATTRIBUTE_NAME:          // attribute name
373       switch (myPtr[0]) {
374       case ' ' :
375       case '\t':
376       case '\n':
377       case '\r':
378         if (aStartData) goto attr_name;
379         ++ myPtr;
380         continue;
381       case '/' :
382         if (aStartData)
383           myError = "Inexpected end of attribute";
384         else if (myPtr[1] != '>')
385           myError = "Improper element tag termination";
386         else {
387           myPtr += 2;
388 #ifdef OCCT_DEBUG
389           theData.Clear();
390           theData << myElement->GetTagName();
391 #endif
392           return XML_FULL_ELEMENT;
393         }
394         return XML_UNKNOWN;
395       case '>' :
396         if (aStartData) {
397           myError = "Inexpected end of attribute";
398           return XML_UNKNOWN;
399         }
400         ++ myPtr;
401 #ifdef OCCT_DEBUG
402         theData.Clear();
403         theData << myElement->GetTagName();
404 #endif
405         return XML_START_ELEMENT;
406       default  :
407         if (::isName (myPtr, myEndPtr, aNameEnd) == Standard_False)
408           if (theData.Length() == 0 || aNameEnd != myPtr) {
409             myError = "Invalid attribute name";
410             return XML_UNKNOWN;
411           }
412         if (aNameEnd >= myEndPtr)
413           aStartData = myPtr;
414         else {
415           if (theData.Length() == 0)
416             anAttrName = LDOMBasicString(myPtr, (Standard_Integer)(aNameEnd - myPtr), myDocument);
417           else {
418             theData.rdbuf()->sputn(myPtr, aNameEnd - myPtr);
419 attr_name:
420             char* aDataString = (char *)theData.str();
421             theData.Clear();
422             anAttrName = LDOMBasicString (aDataString, myDocument);
423             delete [] aDataString;
424           }
425           aStartData = NULL;
426           aState = STATE_ATTRIBUTE_EQUAL;
427         }
428         myPtr = aNameEnd;
429         continue;
430       }
431     case STATE_ATTRIBUTE_EQUAL:          // attribute 'equal' sign
432       switch (myPtr[0]) {
433       case '=' :
434         aState = STATE_ATTRIBUTE_VALUE;
435         Standard_FALLTHROUGH
436       case ' ' :
437       case '\t':
438       case '\n':
439       case '\r':
440         ++ myPtr;
441         continue;
442       default:
443         myError = "Equal sign expected in attribute definition";
444         return XML_UNKNOWN;
445       }
446
447     case STATE_ATTRIBUTE_VALUE:          // attribute value
448       switch (myPtr[0]) {
449       case ' ' :
450       case '\t':
451       case '\n':
452       case '\r':
453         if (aStartData == NULL) {
454           ++ myPtr;
455           continue;
456       default:
457           if (anAttDelimiter == '\0') {
458             myError = "Expected an attribute value";
459             return XML_UNKNOWN;
460       case '\"':
461       case '\'':
462             if (aStartData == NULL) {
463               aStartData     = &myPtr[1];
464               anAttDelimiter = myPtr[0];
465             }
466           }
467         }
468         //      Limitation: we do not take into account that '<' and '&'
469         //      are not allowed in attribute values
470         aPtr = (const char *) memchr (aStartData, anAttDelimiter,
471                                       myEndPtr - aStartData);
472         if (aPtr) {
473           (char&) aPtr[0] = '\0';
474           anAttDelimiter  = '\0';
475           char          * aDataString   = (char *) aStartData;
476           const char    * ePtr          = aPtr;
477
478           //    Append the end of the string to previously taken data
479           if (theData.Length() > 0) {
480             theData.rdbuf()->sputn(aStartData, aPtr-aStartData);
481             aDataString = (char *)theData.str();
482             ePtr = strchr (aDataString, '\0');
483           }
484
485           Standard_Integer aDataLen;
486           aDataString = LDOM_CharReference::Decode (aDataString, aDataLen);
487           if (IsDigit(aDataString[0])) {
488             if (getInteger (anAttrValue, aDataString, ePtr))
489               anAttrValue = LDOMBasicString (aDataString,aDataLen,myDocument);
490           } else
491             anAttrValue = LDOMBasicString (aDataString, aDataLen, myDocument);
492
493           if (theData.Length() > 0) {
494             theData.Clear();
495             delete [] aDataString;
496           }
497           //    Create an attribute
498           myLastChild = myElement -> AddAttribute (anAttrName, anAttrValue,
499                                                    myDocument, myLastChild);
500           myPtr = aPtr + 1;
501           aStartData = NULL;
502           aState = STATE_ATTRIBUTE_NAME;
503         }
504         else {
505           myPtr = myEndPtr;
506           aHasRead = Standard_False;
507         }
508         continue;
509       }
510         // Checking the characters in STATE_ELEMENT_END, seek for ">"
511         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
512     case STATE_ELEMENT_END:
513       aPtr = (const char *) memchr (aStartData, '>', myEndPtr - aStartData);
514       if (aPtr) {
515         // The end of the end-element markup
516         theData.rdbuf()->sputn(aStartData, aPtr - aStartData);
517         myPtr = aPtr + 1;
518         return XML_END_ELEMENT;
519       }
520       myPtr = myEndPtr;
521       aHasRead = Standard_False;
522       continue;
523     }
524   }
525   if (aState != STATE_WAITING) {
526     myError = "Unexpected end of file";
527     return XML_UNKNOWN;
528   }
529   return XML_EOF;
530 }
531
532 //=======================================================================
533 //function : isName
534 //type     : static
535 //purpose  : Check if aString is a valid XML Name
536 //=======================================================================
537
538 static Standard_Boolean isName (const char  * aString,
539                                 const char  * aStringEnd,
540                                 const char  *& aNameEnd)
541 {
542   Standard_Boolean aResult;
543   char aCh = aString[0];
544   if (IsAlphabetic(aCh) || aCh == '_' || aCh == ':') {
545     const char * aPtr = &aString[1];
546     while (aPtr < aStringEnd) {
547       aCh = * aPtr;
548       switch (aCh) {
549       case ' ' :
550       case '\n':
551       case '\r':
552       case '\t':
553       case '=' :
554       case '\0':
555       case '/' :
556       case '>' :
557         aNameEnd = aPtr;
558         return Standard_True;
559       default:
560         if (IsAlphanumeric(aCh) == 0) {
561           aNameEnd = aPtr;
562           return Standard_False;
563         }
564         Standard_FALLTHROUGH
565       case '.' :
566       case '-' :
567       case '_' :
568       case ':' :
569         ++ aPtr;
570       }
571     }
572     aNameEnd = aPtr;
573     aResult = Standard_True;
574   } else {
575     aNameEnd = aString;
576     aResult = Standard_False;
577   }
578   return aResult;
579 }
580
581 //=======================================================================
582 //function : CreateElement
583 //purpose  : 
584 //=======================================================================
585 void LDOM_XmlReader::CreateElement( const char *theName, const Standard_Integer theLen )
586 {
587   myElement = &LDOM_BasicElement::Create (theName, theLen, myDocument);
588 }
589
590 //=======================================================================
591 //function : getInteger
592 //purpose  : Try to initialize theValue as Integer; return False on success
593 //=======================================================================
594
595 Standard_Boolean LDOM_XmlReader::getInteger (LDOMBasicString&    theValue,
596                                              const char          * theStart,
597                                              const char          * theEnd)
598 {
599   char * ptr;
600   errno = 0;
601   if (theEnd - theStart == 1 || theStart[0] != '0')
602   {
603       long aResult = strtol (theStart, &ptr, 10);
604       if (ptr == theEnd && errno == 0) 
605       {
606         theValue = Standard_Integer(aResult);
607         return Standard_False;
608       }
609   }
610   return Standard_True;
611 }