// StdioFileEx.cpp: implementation of the CStdioFileEx class.
//
// Version 1.1 23 August 2003. Incorporated fixes from Dennis Jeryd.
// Version 1.3 19 February 2005. Incorporated fixes Howard J Oh and some of my own.
//
// Copyright David Pritchard 2003-2005. davidpritchard@ctv.es
//
// You can use this class freely, but please keep my ego happy
// by leaving this comment in place.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "StdioFileEx.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
/*static*/ const UINT CStdioFileEx::modeWriteUnicode = 0x20000; // Add this flag to write in Unicode
CStdioFileEx::CStdioFileEx(): CStdioFile()
{
m_bIsUnicodeText = false;
}
CStdioFileEx::CStdioFileEx(LPCTSTR lpszFileName,UINT nOpenFlags)
:CStdioFile(lpszFileName, ProcessFlags(lpszFileName, nOpenFlags))
{
}
BOOL CStdioFileEx::Open(LPCTSTR lpszFileName,UINT nOpenFlags,CFileException* pError /*=NULL*/)
{
// Process any Unicode stuff
ProcessFlags(lpszFileName, nOpenFlags);
return CStdioFile::Open(lpszFileName, nOpenFlags, pError);
}
BOOL CStdioFileEx::ReadString(CString& rString)
{
BOOL bReadData;
LPTSTR lpsz;
int nLen = 0; //, nMultiByteBufferLength = 0, nChars = 0;
CString sTemp;
// If at position 0, discard byte-order mark before reading
if (!m_pStream || (GetPosition() == 0 && m_bIsUnicodeText))
{
wchar_t cDummy;
Read(&cDummy, sizeof(wchar_t));
}
// If compiled for Unicode
#ifdef _UNICODE
// Do standard stuff -- both ANSI and Unicode cases seem to work OK
bReadData = CStdioFile::ReadString(rString);
#else
if (!m_bIsUnicodeText)
{
// Do standard stuff -- read ANSI in ANSI
bReadData = CStdioFile::ReadString(rString);
}
else
{
const int nMAX_LINE_CHARS = 4096;
wchar_t* pszUnicodeString = new wchar_t[nMAX_LINE_CHARS];
char* pszMultiByteString= new char[nMAX_LINE_CHARS];
// Read as Unicode, convert to ANSI; fixed by Dennis Jeryd 6/8/03
bReadData = (NULL != fgetws(pszUnicodeString, nMAX_LINE_CHARS, m_pStream));
if (GetMultiByteStringFromUnicodeString(pszUnicodeString, pszMultiByteString, nMAX_LINE_CHARS))
{
rString = (CString)pszMultiByteString;
}
if (pszUnicodeString)
{
delete pszUnicodeString;
}
if (pszMultiByteString)
{
delete pszMultiByteString;
}
}
#endif
// Then remove end-of-line character if in Unicode text mode
if (bReadData)
{
// Copied from FileTxt.cpp but adapted to Unicode and then adapted for end-of-line being just '\r'.
nLen = rString.GetLength();
if (nLen > 1 && rString.Mid(nLen-2) == sNEWLINE)
{
rString.GetBufferSetLength(nLen-2);
}
else
{
lpsz = rString.GetBuffer(0);
if (nLen != 0 && (lpsz[nLen-1] == _T('\r') || lpsz[nLen-1] == _T('\n')))
{
rString.GetBufferSetLength(nLen-1);
}
}
}
return bReadData;
}
// --------------------------------------------------------------------------------------------
//
// CStdioFileEx::WriteString()
//
// --------------------------------------------------------------------------------------------
// Returns: void
// Parameters: LPCTSTR lpsz
//
// Purpose: Writes string to file either in Unicode or multibyte, depending on whether the caller specified the
// CStdioFileEx::modeWriteUnicode flag. Override of base class function.
// Notes: If writing in Unicode we need to:
// a) Write the Byte-order-mark at the beginning of the file
// b) Write all strings in byte-mode
// - If we were compiled in Unicode, we need to convert Unicode to multibyte if
// we want to write in multibyte
// - If we were compiled in multi-byte, we need to convert multibyte to Unicode if
// we want to write in Unicode.
// Exceptions: None.
//
void CStdioFileEx::WriteString(LPCTSTR lpsz)
{
// If writing Unicode and at the start of the file, need to write byte mark
if (m_nFlags & CStdioFileEx::modeWriteUnicode)
{
// If at position 0, write byte-order mark before writing anything else
if (!m_pStream || GetPosition() == 0)
{
wchar_t cBOM = (wchar_t)nUNICODE_BOM;
CFile::Write(&cBOM, sizeof(wchar_t));
}
}
// If compiled in Unicode...
#ifdef _UNICODE
// If writing Unicode, no conversion needed
if (m_nFlags & CStdioFileEx::modeWriteUnicode)
{
// Write in byte mode
CFile::Write(lpsz, lstrlen(lpsz) * sizeof(wchar_t));
}
// Else if we don't want to write Unicode, need to convert
else
{
int nChars = lstrlen(lpsz) + 1; // Why plus 1? Because yes
int nBufferSize = nChars * sizeof(char);
wchar_t* pszUnicodeString = new wchar_t[nChars];
char * pszMultiByteString= new char[nChars];
int nCharsWritten = 0;
// Copy string to Unicode buffer
lstrcpy(pszUnicodeString, lpsz);
// Get multibyte string
nCharsWritten =
GetMultiByteStringFromUnicodeString(pszUnicodeString, pszMultiByteString, ( short ) nBufferSize, GetACP());
if (nCharsWritten > 0)
{
// CFile::Write((const void*)pszMultiByteString, lstrlen(lpsz));
// Do byte-mode write using actual chars written (fix by Howard J Oh)
CFile::Write((const void*)pszMultiByteString,
nCharsWritten*sizeof(char));
}
if (pszUnicodeString && pszMultiByteString)
{
delete [] pszUnicodeString;
delete [] pszMultiByteString;
}
}
// Else if *not* compiled in Unicode
#else
// If writing Unicode, need to convert
if (m_nFlags & CStdioFileEx::modeWriteUnicode)
{
int nChars = lstrlen(lpsz) + 1; // Why plus 1? Because yes
wchar_t* pszUnicodeString = new wchar_t[nChars];
char * pszMultiByteString= new char[nChars];
int nCharsWritten = 0;
// Copy string to multibyte buffer
lstrcpy(pszMultiByteString, lpsz);
nCharsWritten =
GetUnicodeStringFromMultiByteString(pszMultiByteString,
pszUnicodeString, nChars, GetACP());
if (nCharsWritten > 0)
{
// CFile::Write(pszUnicodeString, lstrlen(lpsz) * sizeof(wchar_t));
// Write in byte mode. Write actual number of chars written * bytes (fix by Howard J Oh)
CFile::Write(pszUnicodeString, nCharsWritten*sizeof(wchar_t));
}
else
{
ASSERT(false);
}
if (pszUnicodeString && pszMultiByteString)
{
delete [] pszUnicodeString;
delete [] pszMultiByteString;
}
}
// Else if we don't want to write Unicode, no conversion needed
else
{
// Do standard stuff
//CStdioFile::WriteString(lpsz);
// Do byte-mode write. This avoids annoying "interpretation" of \n's as \r\n
CFile::Write((const void*)lpsz, lstrlen(lpsz)*sizeof(char));
}
#endif
}
UINT CStdioFileEx::ProcessFlags(const CString& sFilePath, UINT& nOpenFlags)
{
m_bIsUnicodeText = false;
// If we have writeUnicode we must have write or writeRead as well
#ifdef _DEBUG
if (nOpenFlags & CStdioFileEx::modeWriteUnicode)
{
ASSERT(nOpenFlags & CFile::modeWrite || nOpenFlags & CFile::modeReadWrite);
}
#endif
// If reading in text mode and not creating... ; fixed by Dennis Jeryd 6/8/03
if (nOpenFlags & CFile::typeText && !(nOpenFlags & CFile::modeCreate) && !(nOpenFlags & CFile::modeWrite ))
{
m_bIsUnicodeText = IsFileUnicode(sFilePath);
// If it's Unicode, switch to binary mode
if (m_bIsUnicodeText)
{
nOpenFlags ^= CFile::typeText;
nOpenFlags |= CFile::typeBinary;
}
}
m_nFlags = nOpenFlags;
return nOpenFlags;
}
// --------------------------------------------------------------------------------------------
//
// CStdioFileEx::IsFileUnicode()
//
// --------------------------------------------------------------------------------------------
// Returns: bool
// Parameters: const CString& sFilePath
//
// Purpose: Determines whether a file is Unicode by reading the first character and detecting
// whether it's the Unicode byte marker.
// Notes: None.
// Exceptions: None.
//
/*static*/ bool CStdioFileEx::IsFileUnicode(const CString& sFilePath)
{
CFile file;
bool bIsUnicode = false;
wchar_t cFirstChar;
CFileException exFile;
// Open file in binary mode and read first character
if (file.Open(sFilePath, CFile::typeBinary | CFile::modeRead, &exFile))
{
// If byte is Unicode byte-order marker, let's say it's Unicode
if (file.Read(&cFirstChar, sizeof(wchar_t)) > 0 && cFirstChar == (wchar_t)nUNICODE_BOM)
{
bIsUnicode = true;
}
file.Close();
}
else
{
// Handle error here if you like
}
return bIsUnicode;
}
unsigned long CStdioFileEx::GetCharCount()
{
int nCharSize;
unsigned long nByteCount, nCharCount = 0;
if (m_pStream)
{
// Get size of chars in file
nCharSize = m_bIsUnicodeText ? sizeof(wchar_t): sizeof(char);
// If Unicode, remove byte order mark from count
nByteCount = GetLength();
if (m_bIsUnicodeText)
{
nByteCount = nByteCount - sizeof(wchar_t);
}
// Calc chars
nCharCount = (nByteCount / nCharSize);
}
return nCharCount;
}
// --------------------------------------------------------------------------------------------
//
// CStdioFileEx::GetUnicodeStringFromMultiByteString()
//
// --------------------------------------------------------------------------------------------
// Returns: int - num. of chars written (0 means error)
// Parameters: char * szMultiByteString (IN) Multi-byte input string
// wchar_t* szUnicodeString (OUT) Unicode outputstring
// int& nUnicodeBufferSize (IN/OUT) Size of Unicode output buffer(IN)
// Actual bytes written to buffer (OUT)
// UINT nCodePage (IN) Code page used to perform conversion
// Default = -1 (Get local code page).
//
// Purpose: Gets a Unicode string from a MultiByte string.
// Notes: None.
// Exceptions: None.
//
int CStdioFileEx::GetUnicodeStringFromMultiByteString(IN char * szMultiByteString, OUT wchar_t* szUnicodeString, IN OUT int& nUnicodeBufferSize, IN UINT nCodePage)
{
int nCharsWritten = 0;
if (szUnicodeString && szMultiByteString)
{
// If no code page specified, take default for system
if (nCodePage == -1)
{
nCodePage = GetACP();
}
try
{
// Zero out buffer first. NB: nUnicodeBufferSize is NUMBER OF CHARS, NOT BYTES!
memset((void*)szUnicodeString, '\0', sizeof(wchar_t) *
nUnicodeBufferSize);
nCharsWritten = MultiByteToWideChar(nCodePage,MB_PRECOMPOSED,szMultiByteString,-1,szUnicodeString,nUnicodeBufferSize);
}
catch(...)
{
TRACE(_T("Controlled exception in MultiByteToWideChar!\n"));
}
}
// Now fix nCharsWritten
if (nCharsWritten > 0)
{
nCharsWritten--;
}
ASSERT(nCharsWritten > 0);
return nCharsWritten;
}
// --------------------------------------------------------------------------------------------
//
// CStdioFileEx::GetMultiByteStringFromUnicodeString()
//
// --------------------------------------------------------------------------------------------
// Returns: int - number of characters written. 0 means error
// Parameters: wchar_t * szUnicodeString (IN) Unicode input string
// char* szMultiByteString (OUT) Multibyte output string
// short nMultiByteBufferSize (IN) Multibyte buffer size
// UINT nCodePage (IN) Code page used to perform conversion
// Default = -1 (Get local code page).
//
// Purpose: Gets a MultiByte string from a Unicode string
// Notes: None.
// Exceptions: None.
//
int CStdioFileEx::GetMultiByteStringFromUnicodeString(wchar_t * szUnicodeString, char* szMultiByteString,
short nMultiByteBufferSize, UINT nCodePage)
{
BOOL bUsedDefChar = FALSE;
int nCharsWritten = 0;
if (szUnicodeString && szMultiByteString)
{
// Zero out buffer first
memset((void*)szMultiByteString, '\0', nMultiByteBufferSize);
// If no code page specified, take default for system
if (nCodePage == -1)
{
nCodePage = GetACP();
}
try
{
nCharsWritten = WideCharToMultiByte(nCodePage, WC_COMPOSITECHECK | WC_SEPCHARS,
szUnicodeString,-1, szMultiByteString, nMultiByteBufferSize, sDEFAULT_UNICODE_FILLER_CHAR, &bUsedDefChar);
}
catch(...)
{
TRACE(_T("Controlled exception in WideCharToMultiByte!\n"));
}
}
// Now fix nCharsWritten
if (nCharsWritten > 0)
{
nCharsWritten--;
}
return nCharsWritten;
}