1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/common/textfile.cpp
3 // Purpose:     implementation of wxTextFile class
4 // Author:      Vadim Zeitlin
5 // Modified by:
6 // Created:     03.04.98
7 // RCS-ID:      $Id: textfile.cpp 49298 2007-10-21 18:05:49Z SC $
8 // Copyright:   (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence:     wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11 
12 // ============================================================================
13 // headers
14 // ============================================================================
15 
16 #include  "wx/wxprec.h"
17 
18 #ifdef    __BORLANDC__
19     #pragma hdrstop
20 #endif  //__BORLANDC__
21 
22 #if !wxUSE_FILE || !wxUSE_TEXTBUFFER
23     #undef wxUSE_TEXTFILE
24     #define wxUSE_TEXTFILE 0
25 #endif // wxUSE_FILE
26 
27 #if wxUSE_TEXTFILE
28 
29 #ifndef WX_PRECOMP
30     #include "wx/string.h"
31     #include "wx/intl.h"
32     #include "wx/file.h"
33     #include "wx/log.h"
34 #endif
35 
36 #include "wx/textfile.h"
37 #include "wx/filename.h"
38 #include "wx/buffer.h"
39 
40 // ============================================================================
41 // wxTextFile class implementation
42 // ============================================================================
43 
wxTextFile(const wxString & strFileName)44 wxTextFile::wxTextFile(const wxString& strFileName)
45           : wxTextBuffer(strFileName)
46 {
47 }
48 
49 
50 // ----------------------------------------------------------------------------
51 // file operations
52 // ----------------------------------------------------------------------------
53 
OnExists() const54 bool wxTextFile::OnExists() const
55 {
56     return wxFile::Exists(m_strBufferName);
57 }
58 
59 
OnOpen(const wxString & strBufferName,wxTextBufferOpenMode OpenMode)60 bool wxTextFile::OnOpen(const wxString &strBufferName, wxTextBufferOpenMode OpenMode)
61 {
62     wxFile::OpenMode FileOpenMode;
63 
64     switch ( OpenMode )
65     {
66         default:
67             wxFAIL_MSG( _T("unknown open mode in wxTextFile::Open") );
68             // fall through
69 
70         case ReadAccess :
71             FileOpenMode = wxFile::read;
72             break;
73 
74         case WriteAccess :
75             FileOpenMode = wxFile::write;
76             break;
77     }
78 
79     return m_file.Open(strBufferName.c_str(), FileOpenMode);
80 }
81 
82 
OnClose()83 bool wxTextFile::OnClose()
84 {
85     return m_file.Close();
86 }
87 
88 
OnRead(const wxMBConv & conv)89 bool wxTextFile::OnRead(const wxMBConv& conv)
90 {
91     // file should be opened
92     wxASSERT_MSG( m_file.IsOpened(), _T("can't read closed file") );
93 
94     // read the entire file in memory: this is not the most efficient thing to
95     // do but there is no good way to avoid it in Unicode build because if we
96     // read the file block by block we can't convert each block to Unicode
97     // separately (the last multibyte char in the block might be only partially
98     // read and so the conversion would fail) and, as the file contents is kept
99     // in memory by wxTextFile anyhow, it shouldn't be a big problem to read
100     // the file entirely
101     size_t bufSize = 0,
102            bufPos = 0;
103     char block[1024];
104     wxCharBuffer buf;
105 
106     // first determine if the file is seekable or not and so whether we can
107     // determine its length in advance
108     wxFileOffset fileLength;
109     {
110         wxLogNull logNull;
111         fileLength = m_file.Length();
112     }
113 
114     // some non-seekable files under /proc under Linux pretend that they're
115     // seekable but always return 0; others do return an error
116     const bool seekable = fileLength != wxInvalidOffset && fileLength != 0;
117     if ( seekable )
118     {
119         // we know the required length, so set the buffer size in advance
120         bufSize = fileLength;
121         if ( !buf.extend(bufSize - 1 /* it adds 1 internally */) )
122             return false;
123 
124         // if the file is seekable, also check that we're at its beginning
125         wxASSERT_MSG( m_file.Tell() == 0, _T("should be at start of file") );
126     }
127 
128     for ( ;; )
129     {
130         ssize_t nRead = m_file.Read(block, WXSIZEOF(block));
131 
132         if ( nRead == wxInvalidOffset )
133         {
134             // read error (error message already given in wxFile::Read)
135             return false;
136         }
137 
138         if ( nRead == 0 )
139         {
140             // if no bytes have been read, presumably this is a valid-but-empty file
141             if ( bufPos == 0 )
142                 return true;
143 
144             // otherwise we've finished reading the file
145             break;
146         }
147 
148         if ( seekable )
149         {
150             // this shouldn't happen but don't overwrite the buffer if it does
151             wxCHECK_MSG( bufPos + nRead <= bufSize, false,
152                          _T("read more than file length?") );
153         }
154         else // !seekable
155         {
156             // for non-seekable files we have to allocate more memory on the go
157             if ( !buf.extend(bufPos + nRead - 1 /* it adds 1 internally */) )
158                 return false;
159         }
160 
161         // append to the buffer
162         memcpy(buf.data() + bufPos, block, nRead);
163         bufPos += nRead;
164     }
165 
166     if ( !seekable )
167     {
168         bufSize = bufPos;
169     }
170 
171     const wxString str(buf, conv, bufPos);
172 
173     // there's no risk of this happening in ANSI build
174 #if wxUSE_UNICODE
175     if ( bufSize > 4 && str.empty() )
176     {
177         wxLogError(_("Failed to convert file \"%s\" to Unicode."), GetName());
178         return false;
179     }
180 #endif // wxUSE_UNICODE
181 
182     free(buf.release()); // we don't need this memory any more
183 
184 
185     // now break the buffer in lines
186 
187     // last processed character, we need to know if it was a CR or not
188     wxChar chLast = '\0';
189 
190     // the beginning of the current line, changes inside the loop
191     wxString::const_iterator lineStart = str.begin();
192     const wxString::const_iterator end = str.end();
193     for ( wxString::const_iterator p = lineStart; p != end; p++ )
194     {
195         const wxChar ch = *p;
196         switch ( ch )
197         {
198             case '\n':
199                 // could be a DOS or Unix EOL
200                 if ( chLast == '\r' )
201                 {
202                     if ( p - 1 >= lineStart )
203                     {
204                         AddLine(wxString(lineStart, p - 1), wxTextFileType_Dos);
205                     }
206                     else
207                     {
208                         // there were two line endings, so add an empty line:
209                         AddLine(wxEmptyString, wxTextFileType_Dos);
210                     }
211                 }
212                 else // bare '\n', Unix style
213                 {
214                     AddLine(wxString(lineStart, p), wxTextFileType_Unix);
215                 }
216 
217                 lineStart = p + 1;
218                 break;
219 
220             case '\r':
221                 if ( chLast == '\r' )
222                 {
223                     if ( p - 1 >= lineStart )
224                     {
225                         AddLine(wxString(lineStart, p - 1), wxTextFileType_Mac);
226                     }
227                     // Mac empty line
228                     AddLine(wxEmptyString, wxTextFileType_Mac);
229                     lineStart = p + 1;
230                 }
231                 //else: we don't know what this is yet -- could be a Mac EOL or
232                 //      start of DOS EOL so wait for next char
233                 break;
234 
235             default:
236                 if ( chLast == '\r' )
237                 {
238                     // Mac line termination
239                     if ( p - 1 >= lineStart )
240                     {
241                         AddLine(wxString(lineStart, p - 1), wxTextFileType_Mac);
242                     }
243                     else
244                     {
245                         // there were two line endings, so add an empty line:
246                         AddLine(wxEmptyString, wxTextFileType_Mac);
247                     }
248                     lineStart = p;
249                 }
250         }
251 
252         chLast = ch;
253     }
254 
255     // anything in the last line?
256     if ( lineStart != end )
257     {
258         // add unterminated last line
259         AddLine(wxString(lineStart, end), wxTextFileType_None);
260     }
261 
262     return true;
263 }
264 
265 
OnWrite(wxTextFileType typeNew,const wxMBConv & conv)266 bool wxTextFile::OnWrite(wxTextFileType typeNew, const wxMBConv& conv)
267 {
268     wxFileName fn = m_strBufferName;
269 
270     // We do NOT want wxPATH_NORM_CASE here, or the case will not
271     // be preserved.
272     if ( !fn.IsAbsolute() )
273         fn.Normalize(wxPATH_NORM_ENV_VARS | wxPATH_NORM_DOTS | wxPATH_NORM_TILDE |
274                      wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG);
275 
276     wxTempFile fileTmp(fn.GetFullPath());
277 
278     if ( !fileTmp.IsOpened() ) {
279         wxLogError(_("can't write buffer '%s' to disk."), m_strBufferName.c_str());
280         return false;
281     }
282 
283     size_t nCount = GetLineCount();
284     for ( size_t n = 0; n < nCount; n++ ) {
285         fileTmp.Write(GetLine(n) +
286                       GetEOL(typeNew == wxTextFileType_None ? GetLineType(n)
287                                                             : typeNew),
288                       conv);
289     }
290 
291     // replace the old file with this one
292     return fileTmp.Commit();
293 }
294 
295 #endif // wxUSE_TEXTFILE
296