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