0022484: UNICODE characters support
[occt.git] / src / Message / Message_MsgFile.cxx
1 // Created on: 2001-04-26
2 // Created by: OCC Team
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 #include <Message_MsgFile.hxx>
17
18 #include <NCollection_DataMap.hxx>
19 #include <OSD_Environment.hxx>
20 #include <TCollection_AsciiString.hxx>
21 #include <TCollection_ExtendedString.hxx>
22 #include <Standard_Mutex.hxx>
23
24 #include <stdlib.h>
25 #include <stdio.h>
26
27 typedef NCollection_DataMap<TCollection_AsciiString,TCollection_ExtendedString> Message_DataMapOfExtendedString;
28
29 static Message_DataMapOfExtendedString& msgsDataMap ()
30 {
31   static Message_DataMapOfExtendedString aDataMap;
32   return aDataMap;
33 }
34
35 // mutex used to prevent concurrent access to message registry
36 static Standard_Mutex theMutex;
37
38 typedef enum
39 {
40   MsgFile_WaitingKeyword,
41   MsgFile_WaitingMessage,
42   MsgFile_WaitingMoreMessage,
43   MsgFile_Indefinite
44 } LoadingState;
45
46 //=======================================================================
47 //function : Message_MsgFile
48 //purpose  : Load file from given directories
49 //           theDirName may be represented as list: "/dirA/dirB /dirA/dirC"
50 //=======================================================================
51
52 Standard_Boolean Message_MsgFile::Load (const Standard_CString theDirName,
53                                         const Standard_CString theFileName)
54 {
55   if ( ! theDirName || ! theFileName ) return Standard_False;
56
57   Standard_Boolean ret = Standard_True;
58   TCollection_AsciiString aDirList (theDirName);
59   //  Try to load from all consecutive directories in list
60   for (int i = 1;; i++)
61   {
62     TCollection_AsciiString aFileName = aDirList.Token (" \t\n", i);
63     if (aFileName.IsEmpty()) break;
64 #ifdef WNT
65     aFileName += '\\';
66 #else
67     aFileName += '/';
68 #endif
69     aFileName += theFileName;
70     if ( ! LoadFile (aFileName.ToCString()) ) 
71       ret = Standard_False;
72   }
73   return ret;
74 }
75
76 //=======================================================================
77 //function : getString
78 //purpose  : Takes a TCollection_ExtendedString from Ascii or Unicode
79 //           Strings are left-trimmed; those beginning with '!' are omitted
80 //Called   : from loadFile()
81 //=======================================================================
82
83 template <class _Char> static inline Standard_Boolean
84 getString (_Char *&                     thePtr,
85            TCollection_ExtendedString&  theString,
86            Standard_Integer&            theLeftSpaces)
87 {
88   _Char * anEndPtr = thePtr;
89   _Char * aPtr;
90   Standard_Integer aLeftSpaces;
91
92   do 
93   {
94     //    Skip whitespaces in the beginning of the string
95     aPtr = anEndPtr;
96     aLeftSpaces = 0;
97     for (;;)
98     {
99       _Char aChar = * aPtr;
100       if      (aChar == ' ')  aLeftSpaces++;
101       else if (aChar == '\t') aLeftSpaces += 8;
102       else if (aChar == '\r' || * aPtr == '\n') aLeftSpaces = 0;
103       else break;
104       aPtr++;
105     }
106
107     //    Find the end of the string
108     for (anEndPtr = aPtr; * anEndPtr; anEndPtr++)
109       if (anEndPtr[0] == '\n')
110       {
111         if (anEndPtr[-1] == '\r') anEndPtr--;
112         break;
113       }
114
115   } while (aPtr[0] == '!');
116
117   //    form the result
118   if (aPtr == anEndPtr) return Standard_False;
119   thePtr = anEndPtr;
120   if (*thePtr)
121     *thePtr++ = '\0';
122   theString = TCollection_ExtendedString (aPtr);
123   theLeftSpaces = aLeftSpaces;
124   return Standard_True;
125 }
126
127 //=======================================================================
128 //function : loadFile
129 //purpose  : Static function, fills the DataMap of Messages from Ascii or Unicode
130 //Called   : from LoadFile()
131 //=======================================================================
132
133 template <class _Char> static inline Standard_Boolean loadFile (_Char * theBuffer)
134 {
135   TCollection_AsciiString               aKeyword;
136   TCollection_ExtendedString            aMessage, aString;
137   LoadingState                          aState = MsgFile_WaitingKeyword;
138   _Char                                 * sCurrentString = theBuffer;
139   Standard_Integer                      aLeftSpaces=0, aFirstLeftSpaces = 0;
140
141   //    Take strings one-by-one; comments already screened
142   while (::getString (sCurrentString, aString, aLeftSpaces))
143   {
144     Standard_Boolean isKeyword = (aString.Value(1) == '.');
145     switch (aState)
146     {
147     case MsgFile_WaitingMoreMessage:
148       if (isKeyword)
149         Message_MsgFile::AddMsg (aKeyword, aMessage); // terminate the previous one
150         //      Pass from here to 'case MsgFile_WaitingKeyword'
151       else
152       {
153         //      Add another line to the message already in the buffer 'aMessage'
154         aMessage += '\n';
155         aLeftSpaces -= aFirstLeftSpaces;
156         if (aLeftSpaces > 0) aMessage += TCollection_ExtendedString (aLeftSpaces, ' ');
157         aMessage += aString;
158         break;
159       }
160     case MsgFile_WaitingMessage:
161       if (isKeyword == Standard_False)
162       {
163         aMessage         = aString;
164         aFirstLeftSpaces = aLeftSpaces;         // remember the starting position
165         aState = MsgFile_WaitingMoreMessage;
166         break;
167       }
168       //      Pass from here to 'case MsgFile_WaitingKeyword'
169     case MsgFile_WaitingKeyword:
170       if (isKeyword)
171       {
172         // remove the first dot character and all subsequent spaces + right-trim
173         aKeyword = TCollection_AsciiString (aString.Split(1));
174         aKeyword.LeftAdjust();
175         aKeyword.RightAdjust();
176         aState = MsgFile_WaitingMessage;
177       }
178       break;
179     default:
180       break;
181     }
182   }
183   //    Process the last string still remaining in the buffer
184   if (aState == MsgFile_WaitingMoreMessage)
185     Message_MsgFile::AddMsg (aKeyword, aMessage);
186   return Standard_True;
187 }
188
189 //=======================================================================
190 //function : GetFileSize
191 //purpose  : 
192 //=======================================================================
193
194 static Standard_Integer GetFileSize (FILE *theFile)
195 {
196   if ( !theFile ) return -1;
197
198   // get real file size
199   long nRealFileSize = 0;
200   if ( fseek(theFile, 0, SEEK_END) != 0 ) return -1;
201   nRealFileSize = ftell(theFile);
202   if ( fseek(theFile, 0, SEEK_SET) != 0 ) return -1;
203
204   return (Standard_Integer) nRealFileSize;
205 }
206
207 //=======================================================================
208 //function : LoadFile
209 //purpose  : Load the list of messages from a file
210 //=======================================================================
211
212 Standard_Boolean Message_MsgFile::LoadFile (const Standard_CString theFileName)
213 {
214   if (theFileName == NULL || * theFileName == '\0') return Standard_False;
215
216   //    Open the file
217 #ifdef _WIN32
218   // file name is treated as UTF-8 string
219   TCollection_ExtendedString aFileNameW(theFileName, Standard_True);
220   FILE *anMsgFile = _wfopen ((const wchar_t*)aFileNameW.ToExtString(), L"rb");
221 #else
222   FILE *anMsgFile = fopen (theFileName, "rb");
223 #endif
224   if (!anMsgFile) return Standard_False;
225
226   //    Read the file into memory
227   class Buffer
228   {
229     // self-destructing buffer
230     char *myBuf;
231   public:
232     Buffer (Standard_Integer theSize) : myBuf(new char [theSize]) {}
233     ~Buffer () { delete [] myBuf; }
234     operator char* () const { return myBuf; }
235     char& operator [] (Standard_Integer theInd) { return myBuf[theInd]; }
236   };
237   Standard_Integer aFileSize = GetFileSize (anMsgFile);
238   if (aFileSize <= 0)
239   {
240     fclose (anMsgFile);
241     return Standard_False;
242   }
243   Buffer anMsgBuffer (aFileSize + 2);
244   Standard_Integer nbRead =
245     (Standard_Integer) fread (anMsgBuffer, 1, aFileSize, anMsgFile);
246   fclose (anMsgFile);
247   if (nbRead != aFileSize)
248     return Standard_False;
249   anMsgBuffer[aFileSize] = 0;
250   anMsgBuffer[aFileSize+1] = 0;
251
252   // Read the messages in the file and append them to the global DataMap
253   Standard_Boolean isLittleEndian = (anMsgBuffer[0] == '\xff' && anMsgBuffer[1] == '\xfe');
254   Standard_Boolean isBigEndian    = (anMsgBuffer[0] == '\xfe' && anMsgBuffer[1] == '\xff');
255   if ( isLittleEndian || isBigEndian )
256   {
257     Standard_ExtCharacter * aUnicodeBuffer =
258       (Standard_ExtCharacter *) &anMsgBuffer[2];
259     // Convert Unicode representation to order adopted on current platform
260 #if defined(__sparc) && defined(__sun)
261     if ( isLittleEndian ) 
262 #else
263     if ( isBigEndian ) 
264 #endif
265     {
266       // Reverse the bytes throughout the buffer
267       for (Standard_ExtCharacter * aPtr = aUnicodeBuffer;
268            aPtr < (Standard_ExtCharacter *) &anMsgBuffer[aFileSize]; aPtr++)
269       {
270         unsigned short aWord = *aPtr;
271         *aPtr = (aWord & 0x00ff) << 8 | (aWord & 0xff00) >> 8;
272       }
273     }
274     return ::loadFile (aUnicodeBuffer);
275   }
276   else
277     return ::loadFile ((char*) anMsgBuffer);
278 }
279
280 //=======================================================================
281 //function : LoadFromEnv
282 //purpose  : 
283 //=======================================================================
284 void  Message_MsgFile::LoadFromEnv
285   (const Standard_CString envname,
286    const Standard_CString filename,
287    const Standard_CString ext)
288 {
289   Standard_CString extname = ext;
290   TCollection_AsciiString extstr;
291   if (!extname || extname[0] == '\0') {
292     OSD_Environment extenv("CSF_LANGUAGE");
293     extstr  = extenv.Value();
294     extname = extstr.ToCString();
295   }
296   if (!extname || extname[0] == '\0') extname = "us";
297
298   TCollection_AsciiString filestr(filename);
299   if (envname && envname[0] != '\0') {
300     OSD_Environment envenv(envname);
301     TCollection_AsciiString envstr  = envenv.Value();
302     if (envstr.Length() > 0) {
303       if (envstr.Value(envstr.Length()) != '/') filestr.Insert (1,'/');
304       filestr.Insert (1,envstr.ToCString());
305     }
306   }
307   if (extname[0] != '.') filestr.AssignCat ('.');
308   filestr.AssignCat (extname);
309
310   Message_MsgFile::LoadFile (filestr.ToCString());
311 }
312
313 //=======================================================================
314 //function : AddMsg
315 //purpose  : Add one message to the global table. Fails if the same keyword
316 //           already exists in the table
317 //=======================================================================
318
319 Standard_Boolean Message_MsgFile::AddMsg (const TCollection_AsciiString& theKeyword,
320                                           const TCollection_ExtendedString&  theMessage)
321 {
322   Message_DataMapOfExtendedString& aDataMap = ::msgsDataMap();
323
324   Standard_Mutex::Sentry aSentry (theMutex);
325   aDataMap.Bind (theKeyword, theMessage);
326   return Standard_True;
327 }
328
329 //=======================================================================
330 //function : getMsg
331 //purpose  : retrieve the message previously defined for the given keyword
332 //=======================================================================
333
334 const TCollection_ExtendedString &Message_MsgFile::Msg (const Standard_CString theKeyword)
335 {
336   TCollection_AsciiString aKey((char*)theKeyword);
337   return Msg (aKey);
338
339
340 //=======================================================================
341 //function : HasMsg
342 //purpose  : 
343 //=======================================================================
344
345 Standard_Boolean Message_MsgFile::HasMsg (const TCollection_AsciiString& theKeyword)
346 {
347   Standard_Mutex::Sentry aSentry (theMutex);
348   return ::msgsDataMap().IsBound (theKeyword);
349 }
350
351 //=======================================================================
352 //function : Msg
353 //purpose  : retrieve the message previously defined for the given keyword
354 //=======================================================================
355
356 const TCollection_ExtendedString &Message_MsgFile::Msg (const TCollection_AsciiString& theKeyword)
357 {
358   // find message in the map
359   Message_DataMapOfExtendedString& aDataMap = ::msgsDataMap();
360   Standard_Mutex::Sentry aSentry (theMutex);
361
362   // if message is not found, generate error message and add it to the map to minimize overhead
363   // on consequent calls with the same key
364   if (! aDataMap.IsBound(theKeyword))
365   {
366     // text of the error message can be itself defined in the map
367     static const TCollection_AsciiString aPrefixCode("Message_Msg_BadKeyword");
368     static const TCollection_ExtendedString aDefPrefix("Unknown message invoked with the keyword ");
369     TCollection_AsciiString aErrorMessage = (aDataMap.IsBound(aPrefixCode) ? aDataMap(aPrefixCode) : aDefPrefix);
370     aErrorMessage += theKeyword;
371     aDataMap.Bind (theKeyword, aErrorMessage); // do not use AddMsg() here to avoid mutex deadlock
372   }
373
374   return aDataMap (theKeyword);
375 }