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