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