0031309: Data Exchange - RWObj_Reader fails to read mh03.obj with multi-line syntax
[occt.git] / src / Standard / Standard_ReadLineBuffer.hxx
1 // Copyright (c) 2019 OPEN CASCADE SAS
2 //
3 // This file is part of Open CASCADE Technology software library.
4 //
5 // This library is free software; you can redistribute it and/or modify it under
6 // the terms of the GNU Lesser General Public License version 2.1 as published
7 // by the Free Software Foundation, with special exception defined in the file
8 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
9 // distribution for complete text of the license and disclaimer of any warranty.
10 //
11 // Alternatively, this file may be used under the terms of Open CASCADE
12 // commercial license or contractual agreement.
13
14 #ifndef _Standard_ReadLineBuffer_HeaderFile
15 #define _Standard_ReadLineBuffer_HeaderFile
16
17 #include <iostream>
18 #include <vector>
19
20 //! Auxiliary tool for buffered reading of lines from input stream.
21 class Standard_ReadLineBuffer
22 {
23 public:
24
25   //! Constructor with initialization.
26   //! @param theMaxBufferSizeBytes the length of buffer to read (in bytes)
27   Standard_ReadLineBuffer (size_t theMaxBufferSizeBytes)
28   : myUseReadBufferLastStr(false),
29     myIsMultilineMode     (false),
30     myToPutGapInMultiline (true),
31     myBufferPos           (0),
32     myBytesLastRead       (0)
33   {
34     // allocate read buffer
35     myReadBuffer.resize (theMaxBufferSizeBytes);
36   }
37
38   //! Destructor.
39   virtual ~Standard_ReadLineBuffer() {}
40
41   //! Clear buffer and cached values.
42   void Clear()
43   {
44     myReadBufferLastStr.clear();
45     myUseReadBufferLastStr = false;
46     myIsMultilineMode = false;
47     myToPutGapInMultiline = true;
48     myBufferPos = 0;
49     myBytesLastRead = 0;
50   }
51
52   //! Read next line from the stream.
53   //! @return pointer to the line or NULL on error / end of reading buffer
54   //!         (in case of NULL result theStream should be checked externally to identify the presence of errors).
55   //!          Empty lines will be returned also with zero length.
56   //! @param theLineLength [out] - output parameter defined length of returned line.
57   template<typename Stream_T>
58   const char* ReadLine (Stream_T& theStream,
59                         size_t& theLineLength)
60   {
61     int64_t aReadData = 0;
62     return ReadLine (theStream, theLineLength, aReadData);
63   }
64
65   //! Read next line from the stream.
66   //! @return pointer to the line or NULL on error / end of reading buffer
67   //!         (in case of NULL result theStream should be checked externally to identify the presence of errors).
68   //!          Empty lines will be returned also with zero length.
69   //! @param theLineLength [out] - output parameter defined length of returned line.
70   //! @param theReadData   [out] - output parameter defined the number of elements successfully read from the stream during this call,
71   //!                              it can be zero if no data was read and the line is taken from the buffer.
72   template<typename Stream_T>
73   const char* ReadLine (Stream_T& theStream,
74                         size_t& theLineLength,
75                         int64_t& theReadData)
76   {
77     char* aResultLine = NULL;
78     bool isMultiline = false;
79     theLineLength = 0;
80     theReadData = 0;
81
82     while (aResultLine == NULL)
83     {
84       if (myBufferPos == 0 || myBufferPos >= (myBytesLastRead))
85       {
86         // read new chunk from the stream
87         if (!readStream (theStream, myReadBuffer.size(), myBytesLastRead))
88         {
89           // error during file reading
90           break;
91         }
92
93         theReadData = myBytesLastRead;
94
95         if (myBytesLastRead > 0)
96         {
97           myBufferPos = 0;
98         }
99         else
100         {
101           // end of the stream
102           if (myUseReadBufferLastStr)
103           {
104             theLineLength = myReadBufferLastStr.size();
105             aResultLine = &myReadBufferLastStr.front();
106             myUseReadBufferLastStr = false;
107           }
108           break;
109         }
110       }
111
112       size_t aStartLinePos = myBufferPos;
113       bool isEndLineFound = false;
114
115       // read next line from myReadBuffer
116       while (myBufferPos < myBytesLastRead)
117       {
118         if (myIsMultilineMode
119          && myReadBuffer[myBufferPos] == '\\')
120         {
121           // multi-line syntax
122           if (myBufferPos + 1 == myBytesLastRead
123            || (myBufferPos + 2 == myBytesLastRead
124             && myReadBuffer[myBufferPos + 1] == '\r'))
125           {
126             isMultiline = true;
127             if (myToPutGapInMultiline)
128             {
129               myReadBuffer[myBufferPos] = ' ';
130               if (myBufferPos + 1 != myBytesLastRead)
131               {
132                 myReadBuffer[myBufferPos + 1] = ' ';
133               }
134             }
135           }
136           else if (myReadBuffer[myBufferPos + 1] == '\n'
137                  ||(myReadBuffer[myBufferPos + 1] == '\r'
138                  && myReadBuffer[myBufferPos + 2] == '\n'))
139           {
140             size_t aBufferPos = myBufferPos;
141             myBufferPos = aBufferPos + (myReadBuffer[aBufferPos + 1] == '\r' ? 2 : 1);
142             if (myToPutGapInMultiline)
143             {
144               myReadBuffer[aBufferPos] = ' ';
145               ++aBufferPos;
146             }
147
148             if (myUseReadBufferLastStr)
149             {
150               myReadBufferLastStr.insert (myReadBufferLastStr.end(), myReadBuffer.begin() + aStartLinePos, myReadBuffer.begin() + aBufferPos);
151             }
152             else
153             {
154               myReadBufferLastStr = std::vector<char>(myReadBuffer.begin() + aStartLinePos, myReadBuffer.begin() + aBufferPos);
155               myUseReadBufferLastStr = true;
156             }
157
158             aStartLinePos = myBufferPos + 1;
159           }
160         }
161         else if (myReadBuffer[myBufferPos] == '\n')
162         {
163           if (!isMultiline)
164           {
165             isEndLineFound = true;
166           }
167           else if (myBufferPos == 1 && myReadBuffer[0] == '\r')
168           {
169             myReadBufferLastStr.erase (myReadBufferLastStr.end() - 1);
170             aStartLinePos += 2;
171             isMultiline = false;
172           }
173           else if (myBufferPos == 0)
174           {
175             aStartLinePos += 1;
176             if (myReadBufferLastStr[myReadBufferLastStr.size() - 1] == '\\')
177             {
178               myReadBufferLastStr.erase (myReadBufferLastStr.end() - 1);
179             }
180             else
181             {
182               myReadBufferLastStr.erase (myReadBufferLastStr.end() - 2, myReadBufferLastStr.end());
183             }
184             isMultiline = false;
185           }
186         }
187
188         ++myBufferPos;
189
190         if (isEndLineFound) break;
191       }
192
193       if (isEndLineFound)
194       {
195         if (myUseReadBufferLastStr)
196         {
197           // append current string to the last "unfinished" string of the previous chunk
198           myReadBufferLastStr.insert (myReadBufferLastStr.end(), myReadBuffer.begin() + aStartLinePos, myReadBuffer.begin() + myBufferPos);
199           myUseReadBufferLastStr = false;
200           theLineLength = myReadBufferLastStr.size();
201           aResultLine = &myReadBufferLastStr.front();
202         }
203         else
204         {
205           if (myReadBufferLastStr.size() > 0)
206           {
207             myReadBufferLastStr.clear();
208           }
209           theLineLength = myBufferPos - aStartLinePos;
210           aResultLine = &myReadBuffer.front() + aStartLinePos;
211         }
212         // make string null terminated by replacing '\n' or '\r' (before '\n') symbol to null character.
213         if (theLineLength > 1 && aResultLine[theLineLength - 2] == '\r')
214         {
215           aResultLine[theLineLength - 2] = '\0';
216           theLineLength -= 2;
217         }
218         else
219         {
220           aResultLine[theLineLength - 1] = '\0';
221           theLineLength -= 1;
222         }
223       }
224       else
225       {
226         // save "unfinished" part of string to additional buffer
227         if (aStartLinePos != myBufferPos)
228         {
229           if (myUseReadBufferLastStr)
230           {
231             myReadBufferLastStr.insert (myReadBufferLastStr.end(), myReadBuffer.begin() + aStartLinePos, myReadBuffer.begin() + myBufferPos);
232           }
233           else
234           {
235             myReadBufferLastStr = std::vector<char>(myReadBuffer.begin() + aStartLinePos, myReadBuffer.begin() + myBufferPos);
236             myUseReadBufferLastStr = true;
237           }
238         }
239       }
240     }
241     return aResultLine;
242   }
243
244   //! Returns TRUE when the Multiline Mode is on; FALSE by default.
245   //! Multiline modes joins several lines in file having \ at the end of line:
246   //! @code
247   //!   Line starts here, \ // line continuation character without this comment
248   //!   continues \         // line continuation character without this comment
249   //!   and ends.
250   //! @endcode
251   bool IsMultilineMode() const { return myIsMultilineMode; }
252
253   //! Put gap space while merging lines within multiline syntax, so that the following sample:
254   //! @code
255   //! 1/2/3\      // line continuation character without this comment
256   //! 4/5/6
257   //! @endcode
258   //! Will become "1/2/3 4/5/6" when flag is TRUE, and "1/2/35/5/6" otherwise.
259   bool ToPutGapInMultiline() const { return myToPutGapInMultiline; }
260
261   //! Sets or unsets the multi-line mode.
262   //! @param theMultilineMode [in] multiline mode flag
263   //! @param theToPutGap      [in] put gap space while connecting lines (no gap otherwise)
264   void SetMultilineMode (bool theMultilineMode,
265                          bool theToPutGap = true)
266   {
267     myIsMultilineMode     = theMultilineMode;
268     myToPutGapInMultiline = theToPutGap;
269   }
270
271 protected:
272
273   //! Read from stl stream.
274   //! @return true if reading was finished without errors.
275   bool readStream (std::istream& theStream,
276                    size_t theLen,
277                    size_t& theReadLen)
278   {
279     theReadLen = (size_t )theStream.read (&myReadBuffer.front(), theLen).gcount();
280     return !theStream.bad();
281   }
282
283   //! Read from FILE stream.
284   //! @return true if reading was finished without errors.
285   bool readStream (FILE* theStream,
286                    size_t theLen,
287                    size_t& theReadLen)
288   {
289     theReadLen = ::fread (&myReadBuffer.front(), 1, theLen, theStream);
290     return ::ferror (theStream) == 0;
291   }
292
293 protected:
294
295   std::vector<char> myReadBuffer;           //!< Temp read buffer
296   std::vector<char> myReadBufferLastStr;    //!< Part of last string of myReadBuffer
297   bool              myUseReadBufferLastStr; //!< Flag to use myReadBufferLastStr during next line reading
298   bool              myIsMultilineMode;      //!< Flag to process of the special multi-line case at the end of the line
299   bool              myToPutGapInMultiline;  //!< Flag to put gap space while joining lines in multi-line syntax
300   size_t            myBufferPos;            //!< Current position in myReadBuffer
301   size_t            myBytesLastRead;        //!< The number of characters that were read last time from myReadBuffer.
302 };
303
304 #endif // _Standard_ReadLineBuffer_HeaderFile