Code::Blocks  SVN r11506
encodingdetector.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of the Code::Blocks IDE and licensed under the GNU Lesser General Public License, version 3
3  * http://www.gnu.org/licenses/lgpl-3.0.html
4  *
5  * $Revision: 11083 $
6  * $Id: encodingdetector.cpp 11083 2017-06-04 12:24:03Z fuscated $
7  * $HeadURL: https://svn.code.sf.net/p/codeblocks/code/trunk/src/sdk/encodingdetector.cpp $
8  */
9 
10 #include "sdk_precomp.h"
11 #ifndef CB_PRECOMP
12  #include <wx/fontmap.h>
13  #include <wx/file.h>
14  #include <wx/string.h>
15  #include "manager.h"
16  #include "logmanager.h"
17  #include "configmanager.h"
18 #endif // CB_PRECOMP
19 
20 
21 #include "encodingdetector.h"
22 #include "filemanager.h"
23 
24 #include "nsError.h"
25 #include "nsUniversalDetector.h"
26 
27 #include <wx/encconv.h>
28 
29 /* ----------------------------------------------
30  * Some detection code is borrowed from MadEdit,
31  * but modified to suit C::B. Other portions are
32  * using the Mozilla universal char detector.
33  * ---------------------------------------------- */
34 
35 EncodingDetector::EncodingDetector(const wxString& filename, bool useLog) :
36  nsUniversalDetector(NS_FILTER_ALL),
37  m_IsOK(false),
38  m_UseBOM(false),
39  m_UseLog(useLog),
40  m_BOMSizeInBytes(0),
41  m_ConvStr(wxEmptyString)
42 {
44  m_IsOK = DetectEncoding(filename);
45 }
46 
48  nsUniversalDetector(NS_FILTER_ALL),
49  m_IsOK(false),
50  m_UseBOM(false),
51  m_UseLog(useLog),
54 {
56  m_IsOK = DetectEncoding((wxByte*)fileLdr->GetData(), fileLdr->GetLength());
57 }
58 
59 EncodingDetector::EncodingDetector(const wxByte* buffer, size_t size, bool useLog) :
60  nsUniversalDetector(NS_FILTER_ALL),
61  m_IsOK(false),
62  m_UseBOM(false),
63  m_UseLog(useLog),
66 {
68  m_IsOK = DetectEncoding(buffer, size);
69 }
70 
72 {
73 }
74 
75 void EncodingDetector::Report(const char *aCharset)
76 {
77  m_MozillaResult = cbC2U(aCharset);
78 
79  if (m_UseLog)
80  Manager::Get()->GetLogManager()->DebugLog(F(_T("Mozilla universal detection engine detected '%s'."), m_MozillaResult.wx_str()));
81 
82  if (m_MozillaResult == _T("gb18030")) // hack, because wxWidgets only knows cp936
83  m_MozillaResult = _T("cp936");
84  else if (m_MozillaResult.Contains(wxT("*ASCII*"))) // remove our "specials"
86 }
87 
89 {
90  return m_IsOK;
91 }
92 
94 {
95  return m_UseBOM;
96 }
97 
99 {
100  return m_BOMSizeInBytes;
101 }
102 
104 {
105  return m_Encoding;
106 }
107 
109 {
110  return m_ConvStr;
111 }
112 
113 bool EncodingDetector::DetectEncoding(const wxString& filename, bool convert_to_wxstring)
114 {
115  wxFile file(filename);
116  if (!file.IsOpened())
117  return false;
118 
119  size_t size = file.Length();
120  if (size == 0)
121  {
122  file.Close();
123  return false;
124  }
125 
126  wxByte* buffer = (wxByte*) malloc(sizeof(wxByte) * (size + 4));
127  if (!buffer)
128  {
129  file.Close();
130  return false;
131  }
132  buffer[size + 0] = 0;
133  buffer[size + 1] = 0;
134  buffer[size + 2] = 0;
135  buffer[size + 3] = 0;
136 
137  size_t readBytes = file.Read((void*)buffer, size);
138  bool result = false;
139  if (readBytes > 0)
140  result = DetectEncoding(buffer, size, convert_to_wxstring);
141 
142  file.Close();
143  free(buffer);
144  return result;
145 }
146 
147 bool EncodingDetector::DetectEncoding(const wxByte* buffer, size_t size, bool convert_to_wxstring)
148 {
149  ConfigManager* cfgMgr = Manager::Get()->GetConfigManager(_T("editor"));
150  wxString encname = cfgMgr->Read(_T("/default_encoding"));
151 
152  if (cfgMgr->ReadInt(_T("/default_encoding/use_option"), 0) == 1)
153  {
154  // Bypass C::B's auto-detection
155  m_Encoding = wxFontMapper::Get()->CharsetToEncoding(encname, false);
156 
157  if (m_UseLog)
158  {
159  wxString msg;
160  msg.Printf(_T("Warning: bypassing C::B's auto-detection!\n"
161  "Encoding requested is: %s (ID: %d)"),
162  wxFontMapper::Get()->GetEncodingDescription(m_Encoding).c_str(),
163  m_Encoding);
165  }
166  }
167  else
168  {
169  if (!buffer)
170  return false;
171 
172  // Try our own detection for UTF-16 and UTF-32, the Mozilla-version does not work without BOM
173  if ( DetectEncodingEx(buffer, size) )
174  {
175  if (m_UseBOM && m_UseLog)
176  {
177  wxString msg;
178  msg.Printf(_T("Detected encoding via BOM: %s (ID: %d)"),
179  wxFontMapper::Get()->GetEncodingDescription(m_Encoding).c_str(),
180  m_Encoding);
182  }
183  }
184  else
185  {
186  //{ MOZILLA nsUniversalDetector START
187  // If we still have no results try Mozilla (taken from nsUdetXPCOMWrapper.cpp):
188  Reset(); nsresult res = HandleData((char*)buffer, size);
189  if (res==NS_OK)
190  DataEnd();
191  else
192  {
194  if (m_UseLog)
195  Manager::Get()->GetLogManager()->DebugLog(F(_T("Mozilla universal detection failed with %d."), res));
196  }
197  //} MOZILLA nsUniversalDetector END
198 
199  if ( !m_MozillaResult.IsEmpty() )
201 
203  {
204  wxString enc_name = Manager::Get()->GetConfigManager(_T("editor"))->Read(_T("/default_encoding"), wxLocale::GetSystemEncodingName());
206  if (m_UseLog)
207  {
208  wxString msg;
209  msg.Printf(_T("Text seems to be pure ASCII!\n"
210  "We use user specified encoding: %s (ID: %d)"),
211  wxFontMapper::Get()->GetEncodingDescription(m_Encoding).c_str(),
212  m_Encoding);
214  }
215  }
216 
217  if (m_Encoding < 0)
218  {
219  // Use user-specified one; as a fallback
220  m_Encoding = wxFontMapper::Get()->CharsetToEncoding(encname, false);
221  if (m_UseLog)
222  {
223  wxString msg;
224  msg.Printf(_T("Warning: Using user specified encoding as fallback!\n"
225  "Encoding fallback is: %s (ID: %d)"),
226  wxFontMapper::Get()->GetEncodingDescription(m_Encoding).c_str(),
227  m_Encoding);
229  }
230  }
231 
232  m_UseBOM = false;
233  m_BOMSizeInBytes = 0;
234  }
235  }
236 
237  if (m_UseLog)
238  {
239  wxString msg;
240  msg.Printf(_T("Final encoding detected: %s (ID: %d)"),
241  wxFontMapper::Get()->GetEncodingDescription(m_Encoding).c_str(),
242  m_Encoding);
244  }
245 
246  if (convert_to_wxstring && !ConvertToWxString(buffer, size) && m_UseLog)
247  Manager::Get()->GetLogManager()->DebugLog(_T("Something seriously went wrong while converting file content to wxString!"));
248 
249  return true;
250 }
251 
252 // Stolen from https://github.com/etexteditor/e/blob/master/src/Strings.cpp
253 // and: https://github.com/etexteditor/e/blob/master/src/Utf.cpp
254 // Copyright (c) 2009, Alexander Stigsen, e-texteditor.com (All rights reserved)
255 // http://www.e-texteditor.com/
256 bool EncodingDetector::DetectEncodingEx(const wxByte* buffer, size_t size)
257 {
258  if (!buffer || size == 0) return false;
259 
260  const wxByte* buff_ptr = buffer;
261  const wxByte* buff_end = &buffer[size];
263 
264  // Check if the buffer starts with a BOM (Byte Order Marker)
265  if (size >= 2)
266  {
267  if (size >= 4 && memcmp(buffer, "\xFF\xFE\x00\x00", 4) == 0)
268  {
270  m_BOMSizeInBytes = 4;
271  m_UseBOM = true;
272  }
273  else if (size >= 4 && memcmp(buffer, "\xFE\xFF\x00\x00", 4) == 0)
274  {
275  // FE FF 00 00 UCS-4, unusual octet order BOM (3412)
276  // X-ISO-10646-UCS-4-3412 can not (yet) be handled by wxWidgets
277  enc = (wxFontEncoding)-1;
278  }
279  else if (size >= 4 && memcmp(buffer, "\x00\x00\xFE\xFF", 4) == 0)
280  {
282  m_BOMSizeInBytes = 4;
283  m_UseBOM = true;
284  }
285  else if (size >= 4 && memcmp(buffer, "\x00\x00\xFF\xFE", 4) == 0)
286  {
287  // 00 00 FF FE UCS-4, unusual octet order BOM (2143)
288  // X-ISO-10646-UCS-4-2143 can not (yet) be handled by wxWidgets
289  enc = (wxFontEncoding)-1;
290  }
291  else if ( memcmp(buffer, "\xFF\xFE", 2) == 0)
292  {
294  m_BOMSizeInBytes = 2;
295  m_UseBOM = true;
296  }
297  else if ( memcmp(buffer, "\xFE\xFF", 2) == 0)
298  {
300  m_BOMSizeInBytes = 2;
301  m_UseBOM = true;
302  }
303  else if (size >= 3 && memcmp(buffer, "\xEF\xBB\xBF", 3) == 0)
304  {
305  enc = wxFONTENCODING_UTF8;
306  m_BOMSizeInBytes = 3;
307  m_UseBOM = true;
308  }
309  else if (size >= 5 && memcmp(buffer, "\x2B\x2F\x76\x38\x2D", 5) == 0)
310  {
311  enc = wxFONTENCODING_UTF7;
312  m_BOMSizeInBytes = 5;
313  m_UseBOM = true;
314  }
315 
316  buff_ptr += m_BOMSizeInBytes;
317  }
318 
319  // If the file starts with a leading < (less) sign, it is probably an XML file
320  // and we can determine the encoding by how the sign is encoded.
321  if (enc == wxFONTENCODING_DEFAULT && size >= 2)
322  {
323  if (size >= 4 && memcmp(buffer, "\x3C\x00\x00\x00", 4) == 0) enc = wxFONTENCODING_UTF32LE;
324  else if (size >= 4 && memcmp(buffer, "\x00\x00\x00\x3C", 4) == 0) enc = wxFONTENCODING_UTF32BE;
325  else if ( memcmp(buffer, "\x3C\x00", 2) == 0) enc = wxFONTENCODING_UTF16LE;
326  else if ( memcmp(buffer, "\x00\x3C", 2) == 0) enc = wxFONTENCODING_UTF16BE;
327  }
328 
329  // Unicode Detection
330  if (enc == wxFONTENCODING_DEFAULT)
331  {
332  unsigned int null_byte_count = 0;
333  unsigned int utf_bytes = 0;
334  unsigned int good_utf_count = 0;
335  unsigned int bad_utf_count = 0;
336  unsigned int bad_utf32_count = 0;
337  unsigned int bad_utf16_count = 0;
338  unsigned int nl_utf32le_count = 0;
339  unsigned int nl_utf32be_count = 0;
340  unsigned int nl_utf16le_count = 0;
341  unsigned int nl_utf16be_count = 0;
342 
343  while (buff_ptr != buff_end)
344  {
345  if (*buff_ptr == 0) ++null_byte_count;
346 
347  // Detect UTF-8 by scanning for invalid sequences
348  if (utf_bytes == 0)
349  {
350  if ((*buff_ptr & 0xC0) == 0x80 || *buff_ptr == 0)
351  ++bad_utf_count;
352  else
353  {
354  const char c = *buff_ptr;
355  utf_bytes = 5; // invalid length
356  if ((c & 0x80) == 0x00) utf_bytes = 1;
357  else if ((c & 0xE0) == 0xC0) utf_bytes = 2;
358  else if ((c & 0xF0) == 0xE0) utf_bytes = 3;
359  else if ((c & 0xF8) == 0xF0) utf_bytes = 4;
360  if (utf_bytes > 3)
361  {
362  ++bad_utf_count;
363  utf_bytes = 0;
364  }
365  }
366  }
367  else if ((*buff_ptr & 0xC0) == 0x80)
368  {
369  --utf_bytes;
370  if (utf_bytes == 0)
371  ++good_utf_count;
372  }
373  else
374  {
375  ++bad_utf_count;
376  utf_bytes = 0;
377  }
378 
379  // Detect UTF-32 by scanning for newlines (and lack of null chars)
380  if ((wxUIntPtr)buff_ptr % 4 == 0 && buff_ptr+4 <= buff_end)
381  {
382  if (*((wxUint32*)buff_ptr) == 0 ) ++bad_utf32_count;
383  if (*((wxUint32*)buff_ptr) == wxUINT32_SWAP_ON_BE(0x0A)) ++nl_utf32le_count;
384  if (*((wxUint32*)buff_ptr) == wxUINT32_SWAP_ON_LE(0x0A)) ++nl_utf32be_count;
385  }
386 
387  // Detect UTF-16 by scanning for newlines (and lack of null chars)
388  if ((wxUIntPtr)buff_ptr % 2 == 0 && buff_ptr+4 <= buff_end)
389  {
390  if (*((wxUint16*)buff_ptr) == 0) ++bad_utf16_count;
391  if (*((wxUint16*)buff_ptr) == wxUINT16_SWAP_ON_BE(0x0A)) ++nl_utf16le_count;
392  if (*((wxUint16*)buff_ptr) == wxUINT16_SWAP_ON_LE(0x0A)) ++nl_utf16be_count;
393  }
394 
395  ++buff_ptr;
396  }
397 
398  if (bad_utf_count == 0) enc = wxFONTENCODING_UTF8;
399  else if (bad_utf32_count == 0 && nl_utf32le_count > size / 400) enc = wxFONTENCODING_UTF32LE;
400  else if (bad_utf32_count == 0 && nl_utf32be_count > size / 400) enc = wxFONTENCODING_UTF32BE;
401  else if (bad_utf16_count == 0 && nl_utf16le_count > size / 200) enc = wxFONTENCODING_UTF16LE;
402  else if (bad_utf16_count == 0 && nl_utf16be_count > size / 200) enc = wxFONTENCODING_UTF16BE;
403  else if (null_byte_count)
404  return false; // Maybe this is a binary file?
405  }
406 
407  if (enc != wxFONTENCODING_DEFAULT)
408  {
409  m_Encoding = enc; // Success.
410  return true;
411  }
412 
413  // If we can't detect encoding and it does not contain null bytes
414  // just ignore it and try backup-procedures (Mozilla) later...
415  return false;
416 }
417 
419 inline wxString makeStringNoNull(const wxWCharBuffer &wideBuff)
420 {
421  wxString result(wideBuff);
422  if (!result.empty())
423  {
424  wxString::size_type ii = result.find_last_not_of(wxT('\0'));
425  if (ii != wxString::npos)
426  result.resize(ii + 1);
427  }
428  return result;
429 }
430 
431 bool EncodingDetector::ConvertToWxString(const wxByte* buffer, size_t size)
432 {
433  LogManager* logmgr = Manager::Get()->GetLogManager();
434  wxString logmsg;
435 
436  if (!buffer || size == 0)
437  {
438  if (m_UseLog)
439  {
440  logmsg.Printf(_T("Encoding conversion has failed (buffer is empty)!"));
441  logmgr->DebugLog(logmsg);
442  }
443  return false; // Nothing we can do...
444  }
445 
446  if (m_BOMSizeInBytes > 0)
447  {
448  for (int i = 0; i < m_BOMSizeInBytes; ++i)
449  buffer++;
450  }
451 
452  size_t outlen = 0;
453 
454  /* NOTE (Biplab#5#): FileManager returns a buffer with 4 extra NULL chars appended.
455  But the buffer size is returned sans the NULL chars */
456 
457  wxWCharBuffer wideBuff;
458 
459  // if possible use the special conversion-routines, they are much faster than wxCSCov (at least on linux)
461  {
462  wxMBConvUTF7 conv;
463  wideBuff = conv.cMB2WC((const char*)buffer, size + 4 - m_BOMSizeInBytes, &outlen);
464  }
465  else if ( m_Encoding == wxFONTENCODING_UTF8 )
466  {
467  wxMBConvUTF8 conv;
468  wideBuff = conv.cMB2WC((const char*)buffer, size + 4 - m_BOMSizeInBytes, &outlen);
469  }
470  else if ( m_Encoding == wxFONTENCODING_UTF16BE )
471  {
472  wxMBConvUTF16BE conv;
473  wideBuff = conv.cMB2WC((const char*)buffer, size + 4 - m_BOMSizeInBytes, &outlen);
474  }
475  else if ( m_Encoding == wxFONTENCODING_UTF16LE )
476  {
477  wxMBConvUTF16LE conv;
478  wideBuff = conv.cMB2WC((const char*)buffer, size + 4 - m_BOMSizeInBytes, &outlen);
479  }
480  else if ( m_Encoding == wxFONTENCODING_UTF32BE )
481  {
482  wxMBConvUTF32BE conv;
483  wideBuff = conv.cMB2WC((const char*)buffer, size + 4 - m_BOMSizeInBytes, &outlen);
484  }
485  else if ( m_Encoding == wxFONTENCODING_UTF32LE )
486  {
487  wxMBConvUTF32LE conv;
488  wideBuff = conv.cMB2WC((const char*)buffer, size + 4 - m_BOMSizeInBytes, &outlen);
489  }
490  else
491  {
492  // try wxEncodingConverter first, even it it only works for
493  // wxFONTENCODING_ISO8859_1..15, wxFONTENCODING_CP1250..1257 and wxFONTENCODING_KOI8
494  // but it's much, much faster than wxCSConv (at least on Linux)
495  wxEncodingConverter conv;
496  wchar_t* tmp = new wchar_t[size + 4 - m_BOMSizeInBytes];
498  && conv.Convert((const char*)buffer, tmp) )
499  {
500  wideBuff = tmp;
501  outlen = size + 4 - m_BOMSizeInBytes; // should be correct, because Convert has returned true
502  if (m_UseLog && outlen>0)
503  {
504  logmsg.Printf(_T("Conversion succeeded using wxEncodingConverter "
505  "(buffer size = %lu, converted size = %lu."), static_cast<unsigned long>(size), static_cast<unsigned long>(outlen));
506  logmgr->DebugLog(logmsg);
507  }
508  }
509  else
510  {
511  // try wxCSConv, if nothing else works
512  wxCSConv csconv(m_Encoding);
513  if (csconv.IsOk())
514  {
515  wideBuff = csconv.cMB2WC((const char*)buffer, size + 4 - m_BOMSizeInBytes, &outlen);
516  if (m_UseLog && outlen>0)
517  {
518  logmsg.Printf(_T("Conversion succeeded using wxCSConv "
519  "(buffer size = %lu, converted size = %lu."), static_cast<unsigned long>(size), static_cast<unsigned long>(outlen));
520  logmgr->DebugLog(logmsg);
521  }
522  }
523  }
524  delete [] tmp;
525  }
526 
527  if (outlen>0)
528  {
529  m_ConvStr = makeStringNoNull(wideBuff);
530  return true; // Done.
531  }
532 
533  // Here, outlen == 0, so an error occurred during conversion.
534  if (m_UseLog)
535  {
536  logmsg.Printf(_T("Encoding conversion using settings has failed!\n"
537  "Encoding chosen was: %s (ID: %d)"),
538  wxFontMapper::Get()->GetEncodingDescription(m_Encoding).wx_str(),
539  m_Encoding);
540  logmgr->DebugLog(logmsg);
541  }
542 
543  // Try system locale as fall-back (if requested by the settings)
544  ConfigManager* cfgMgr = Manager::Get()->GetConfigManager(_T("editor"));
545  if (cfgMgr->ReadBool(_T("/default_encoding/use_system"), true))
546  {
547  if (platform::windows)
548  {
549  if (m_UseLog)
550  logmgr->DebugLog(_T("Trying system locale as fallback..."));
551 
553  }
554  else
555  {
556  // We can rely on the UTF-8 detection code ;-)
557  if (m_UseLog)
558  logmgr->DebugLog(_T("Trying ISO-8859-1 as fallback..."));
559 
561  }
562 
563  wxCSConv conv_system(m_Encoding);
564  wideBuff = conv_system.cMB2WC((const char*)buffer, size + 4 - m_BOMSizeInBytes, &outlen);
565  m_ConvStr = makeStringNoNull(wideBuff);
566 
567  if (outlen == 0)
568  {
569  if (m_UseLog)
570  {
571  logmsg.Printf(_T("Encoding conversion using system locale fallback has failed!\n"
572  "Last encoding choosen was: %s (ID: %d)\n"
573  "Don't know what to do."),
574  wxFontMapper::Get()->GetEncodingDescription(m_Encoding).c_str(),
575  m_Encoding);
576  logmgr->DebugLog(logmsg);
577  }
578  return false; // Nothing we can do...
579  }
580  }
581  else
582  {
583  if (m_UseLog)
584  {
585  logmgr->DebugLog(_T("Encoding conversion has seriously failed!\n"
586  "Don't know what to do."));
587  }
588  return false; // Nothing we can do...
589  }
590 
591  return true;
592 }
static wxFontEncoding GetEncodingFromName(const wxString &encoding)
wxString F(const wxChar *msg,...)
sprintf-like function
Definition: logmanager.h:20
size_t wxUIntPtr
#define wxUINT32_SWAP_ON_BE(wxUint32_value)
wxFontEncoding m_Encoding
ConfigManager * GetConfigManager(const wxString &name_space) const
Definition: manager.cpp:474
int ReadInt(const wxString &name, int defaultVal=0)
bool DetectEncoding(const wxString &filename, bool convert_to_wxstring=true)
static Manager * Get()
Use Manager::Get() to get a pointer to its instance Manager::Get() is guaranteed to never return an i...
Definition: manager.cpp:182
#define wxUINT16_SWAP_ON_LE(wxUint16_value)
size_t GetLength()
Definition: filemanager.cpp:48
void resize(size_t nSize, wxUniChar ch='\0')
wxFileOffset Length() const
wxUint8 wxByte
bool ReadBool(const wxString &name, bool defaultVal=false)
unsigned short wxUint16
#define _T(string)
bool DetectEncodingEx(const wxByte *buffer, size_t len)
wxFontEncoding
void Report(const char *aCharset) override
#define wxT(string)
#define wxUINT32_SWAP_ON_LE(wxUint32_value)
bool empty() const
wxString makeStringNoNull(const wxWCharBuffer &wideBuff)
Convert the char buffer to wxString and if there are any null-terminating characters at the end - rem...
bool Contains(const wxString &str) const
DLLIMPORT wxString cbC2U(const char *str)
Return str as a proper unicode-compatible string.
Definition: globals.cpp:733
static wxString GetSystemEncodingName()
bool Close()
wxString m_MozillaResult
LogManager * GetLogManager() const
Definition: manager.cpp:439
wxString Read(const wxString &key, const wxString &defaultVal=wxEmptyString)
#define wxUINT16_SWAP_ON_BE(wxUint16_value)
bool IsOpened() const
size_t size_type
wxString GetWxStr() const
size_t find_last_not_of(const wxString &str, size_t nStart=npos) const
const wxStringCharType * wx_str() const
static wxFontEncoding GetSystemEncoding()
unsigned int wxUint32
wxString wxEmptyString
ssize_t Read(void *buffer, size_t count)
EncodingDetector(const wxString &filename, bool useLog=true)
bool IsEmpty() const
static const size_t npos
bool ConvertToWxString(const wxByte *buffer, size_t size)
int GetBOMSizeInBytes() const
bool UsesBOM() const
void DebugLog(const wxString &msg, Logger::level lv=Logger::info)
Definition: logmanager.h:146
char * GetData()
Definition: filemanager.cpp:42
bool Init(wxFontEncoding input_enc, wxFontEncoding output_enc, int method=wxCONVERT_STRICT)
wxFontEncoding GetFontEncoding() const
bool Convert(const char *input, char *output) const
virtual wxFontEncoding CharsetToEncoding(const wxString &charset, bool interactive=true)
static wxFontMapper * Get()
int Printf(const wxString &pszFormat,...)
~EncodingDetector() override
bool IsOk() const